Multithreading pthreads:如果我从两个不同的线程中增加一个全局线程,会有同步问题吗?

Multithreading pthreads:如果我从两个不同的线程中增加一个全局线程,会有同步问题吗?,multithreading,pthreads,Multithreading,Pthreads,假设我有两个线程A和B都在递增A~global~变量“count”。每个线程运行一个for循环,如下所示: for(int i=0; i<1000; i++) count++; //alternatively, count = count + 1; for(int i=0;i是的,在这种情况下可能存在同步问题。您需要使用互斥锁保护count变量,或者使用(通常是特定于平台的)原子操作 使用pthread互斥体的示例 静态pthread\u mutex\u t mutex=pthr

假设我有两个线程A和B都在递增A~global~变量“count”。每个线程运行一个for循环,如下所示:

for(int i=0; i<1000; i++)
    count++; //alternatively, count = count + 1;

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

使用pthread互斥体的示例

静态pthread\u mutex\u t mutex=pthread\u mutex\u初始值设定项;

对于(int i=0;iYes),可能存在同步问题

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

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

+-----+
|   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.
+-----+
所以您可以看到,即使两个线程都试图增加值,但只增加了一个

这只是一个可能的问题,也可能是写本身不是原子的,一个线程在被交换之前可能只更新部分值


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

显然需要使用互斥体或其他同步机制来保护Count

在基本层面上,count++语句分解为:

load count into register
increment register
store count from register
上下文切换可能在这些步骤之前/之后发生,导致以下情况:

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)
如您所见,两个线程都完成了循环的一次迭代,但最终结果是计数只增加了一次

您可能还希望使count易失性,以强制加载和存储进入内存,因为一个好的优化器可能会将count保存在寄存器中,除非另有说明

此外,我还建议,如果这是线程中要完成的所有工作,那么保持一致性所需的所有互斥锁锁定/解锁的性能都会显著下降。线程应该有更大的工作单元来执行

我想,由于语句“count=count+1”可能会分解为两条汇编指令,因此有可能在这两条指令之间交换另一个线程?不确定。您认为如何

不要这样想。您正在编写C代码和pthreads代码。您不必考虑汇编代码来了解代码的行为

pthreads标准没有定义当一个线程访问一个对象,而另一个线程正在或可能正在修改它时的行为。因此,除非您正在编写特定于平台的代码,否则您应该假设此代码可以执行任何操作,甚至崩溃

显而易见的pthreads修复方法是使用互斥锁。如果您的平台有原子操作,您可以使用这些操作


我强烈建议您不要深入讨论它可能如何失败或汇编代码可能是什么样子。无论您认为编译器或CPU可能会做什么,代码的行为都是未定义的。而且很容易让自己相信您已经涵盖了所有您认为它可能会失败和失败的方法如果你错过了一个,它就失败了。

你举例说明的问题在现代CPU上几乎没有意义。通常问题不在于以原子方式更新多个字节,因为机器通常会以原子方式一次更新一个字。此外,你所需要做的只是一个不稳定的关键字。这是你在实践中经常看到的问题e要简单得多:两个线程读取相同的值,每个线程在寄存器中独立递增该值,然后写回相同的值。@blucz,我提供了一个更简单的情况(在您键入注释的过程中,从外观上看)但你完全错了。POSIX线程不会对其运行的硬件以及其写入是否为原子级进行假设。如果你要遵循一个标准,你应该遵循它。如果你想引入进一步的假设,那没关系,只需了解其后果,不要假装遵循标准:-)我想你误解了我的评论。我没有说任何关于pthread标准的事情。我只是建议在实践中,你给出的例子不太可能,事实确实如此,因为我给出的原因。有可能会有这样的错误吗?当然……在一个99.999%的人都看不到的系统上。是的,不幸的是,我每天都在处理这样一个怪兽然而,它几乎可以肯定地负责跟踪进入或退出银行账户的每一分钱:-)Systemz大型机非常擅长发现做出不必要假设的代码,比如那些假设字符
A
z
是连续的。99.999%的人永远看不到这一点,这并不意味着如果出现问题,它只会影响0.001%。但是,尽管如此,这是无关紧要的,因为我已经用更可能的可能性取代了这种可能性。这并没有降低早期问题的严重性。正如我所说的,引导尤达:“不。试着不要!做。或者不做!”-要么遵循标准,要么大多数时候不做,你是对的,这可能不重要,但我更愿意安全。其他的可能有不同的优先级。Volatile不会强制加载和存储进入内存,而不是以这里暗示的方式。Volatile让编译器知道,对于所讨论的变量,其值可以在编译器不知道的情况下更改,例如,实时时钟。这意味着编译器将不依赖该值的寄存器副本。然而,它并不能阻止CPU尽可能长时间地延迟缓存线对内存的写入(这就是它将要做的,因为内存写入会破坏性能)。这个答案真的不正确。单薄
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)