您假设操作是原子的。现在假设它们不是,在实际系统中就是这种情况。
作为一个全局变量,count 可以被系统中的所有线程访问。这意味着,在没有同步的情况下,所有线程将以非确定性和交错的方式执行以下操作:
for(int i = 0; i < 1000; i++){
aux = count;
aux++;
usleep(random() % 10);
count = aux;
}
总而言之,在循环的每次迭代中,每个线程都会在给定的时刻拥有 count 值的副本,递增该副本 (aux++),然后将 count 分配给本地值 count = aux; .
问题 1:每个线程读取的值 count 在线程执行时可能因线程而异,因为一个线程可能正在读取一个值,该值在紧接之后的某个时间瞬间被另一个(或多个)线程修改(请记住,操作不是原子的,可以以交错的方式执行)。
问题 2:分配给 count 的值不受任何锁定机制的保护,这意味着多个线程可能以交错方式甚至同时执行该指令(例如,在多处理器系统中这是可能的) .这意味着正在执行的线程之一(您不知道它是什么)会将count = aux 中的count 的值设置为aux。
一个可能的执行场景的简单示例:
例如,让我们假设三个线程。线程 1 读取值 count = 100 并被抢占。线程 2 读取值 100 并执行一段时间,将计数设置为(比如说)300,然后它被抢占。最后,线程 3 读取值 300 并执行一些循环迭代。如果线程 1 再次执行并设置值 count = aux,则在一次循环迭代后,值将设置为 101。看看问题!
需要同步以确保只有一个线程在执行读取、递增和赋值,实际上是为了使操作表现得好像它们是原子的。
问:如果我在 for 循环之前放置一个信号量 sem_wait,在 for 循环之后放置 sem_post,这是否意味着我的线程不再并行运行?
答:这意味着每个线程将交错执行 for 循环。例如,线程 1 将执行 for 循环的 100 次迭代,线程 2 将执行 200 次迭代等。请记住:调度程序控制每个线程的执行,因此迭代次数不受用户控制。您的代码是同步的,但不是以理想的方式。
问:我应该将sem_wait 和sem_post 放在哪里才能使我的线程正确同步?
答:您应该将信号量用于尽可能少的需要同步的操作,以便从并发/并行代码执行中获得最大收益。例如,使用信号量,您的代码可能是:
for(int i = 0; i < 1000; i++){
sem_wait(...);
count++;
sem_post(...);
}
您不再需要aux,因为信号量确保只有一个线程在增加count 的值。
注意,当您使用线程时,您也可以使用互斥锁代替信号量。
我希望这能澄清你的疑虑。