pthreads:如果我从两个不同的线程增加一个全局,是否会出现同步问题?

min*_*ief 4 multithreading pthreads

假设我有两个线程A和B都递增一个~global~variable"count".每个线程像这样运行一个for循环:

for(int i=0; i<1000; i++)
    count++; //alternatively, count = count + 1;
Run Code Online (Sandbox Code Playgroud)

即每个线程增量计数1000次,并且假设计数从0开始.在这种情况下是否会出现同步问题?或者在执行完成后计数正确等于2000?我想因为语句"count = count + 1"可能会分解成两个汇编指令,所以在这两个指令之间有可能交换另一个线程吗?不确定.你怎么看?

blu*_*ucz 11

是的,在这种情况下可能存在同步问题.您需要使用互斥锁保护count变量,或使用(通常是特定于平台的)原子操作.

使用pthread互斥锁的示例

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

for(int i=0; i<1000; i++) {
    pthread_mutex_lock(&mutex);
    count++;
    pthread_mutex_unlock(&mutex);
}
Run Code Online (Sandbox Code Playgroud)

使用原子操作

这里有一个关于平台特定原子操作的事先讨论: UNIX便携式原子操作

如果您只需要支持GCC,这种方法很简单.如果您支持其他编译器,您可能必须做出每个平台的决策.


pax*_*blo 5

是的,可能存在同步问题。

作为可能问题的一个示例,不能保证增量本身是原子操作。

换句话说,如果一个线程读取增量值然后被换出,另一个线程可以进来并更改它,那么第一个线程将写回错误的值:

+-----+
|   0 | Value stored in memory (0).
+-----+
|   0 | Thread 1 reads value into register (r1 = 0).
+-----+
|   0 | Thread 2 reads value into register (r2 = 0).
+-----+
|   1 | Thread 2 increments r2 and writes back.
+-----+
|   1 | Thread 1 increments r1 and writes back.
+-----+
Run Code Online (Sandbox Code Playgroud)

因此您可以看到,即使两个线程都尝试增加该值,它也只增加了 1。

这只是可能出现的问题之一也可能是写入本身不是原子的,并且一个线程在被换出之前可能仅更新部分值。

如果您有保证在您的实现中起作用的原子操作,则可以使用它们。否则,请使用互斥锁,这就是 pthreads 提供的同步功能(并且保证有效),因此是最安全的方法。

  • @blucz,我提供了一个更简单的情况(当您从外观上输入评论时正在进行中),但您大错特错了。POSIX 线程不会对它们运行的​​硬件以及其写入是否是原子的做出假设。如果你要遵循一个标准,你就应该遵循它。如果您想引入进一步的假设,没关系,只需注意后果即可,不要假装遵循标准:-) (2认同)

Dre*_*all 5

计数显然需要通过互斥体或其他同步机制来保护。

从根本上来说,count++ 语句可分解为:

load count into register
increment register
store count from register
Run Code Online (Sandbox Code Playgroud)

上下文切换可能发生在任何这些步骤之前/之后,从而导致以下情况:

Thread 1:  load count into register A (value = 0)
Thread 2:  load count into register B (value = 0)
Thread 1:  increment register A (value = 1)
Thread 1:  store count from register A (value = 1)
Thread 2:  increment register B (value = 1)
Thread 2:  store count from register B (value = 1)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,两个线程都完成了一次循环迭代,但最终结果是 count 只增加了一次。

您可能还希望使计数变得易失,以强制加载和存储到内存中,因为一个好的优化器可能会将计数保留在寄存器中,除非另有说明。

另外,我建议,如果这就是您的线程中要完成的所有工作,那么保持一致所需的所有互斥锁定/解锁的性能将急剧下降。线程应该有更大的工作单元来执行。