C 线程之间能否安全地共享整数?

C 线程之间能否安全地共享整数?,c,thread-safety,pthreads,C,Thread Safety,Pthreads,在没有任何同步实用程序的C程序中,多个线程在pthreads之间使用相同的整数内存位置是否存在问题 为了简化问题, 只有一个线程将写入整数 多个线程将读取该整数 这个伪C说明了我的想法 void thread_main(int *a) { //wait for something to finish //dereference 'a', make decision based on its value } int value = 0; for (int i=0; i<10;

在没有任何同步实用程序的C程序中,多个线程在pthreads之间使用相同的整数内存位置是否存在问题

为了简化问题,

  • 只有一个线程将写入整数
  • 多个线程将读取该整数
这个伪C说明了我的想法

void thread_main(int *a) {
  //wait for something to finish
  //dereference 'a', make decision based on its value
}

int value = 0;

for (int i=0; i<10; i++)
  pthread_create(NULL,NULL,thread_main,&value);
}
// do something
value = 1;
void thread\u main(int*a){
//等待某事完成
//取消引用“a”,根据其值进行决策
}
int值=0;

对于(inti=0;i您的伪代码是不安全的

尽管访问字号大小的整数确实是原子的,这意味着您将永远看不到中间值,但是“写入前”或“写入后”,这对于您的算法来说是不够的

您依赖于写入
a
的相对顺序,并进行一些其他更改来唤醒线程。这不是原子操作,在现代处理器上也不能保证


你需要某种内存限制来防止写重新排序。否则就不能保证其他线程会看到新值。

我不会指望它。编译器可能会发出代码,假定它知道CPU寄存器中“value”的值在任何给定时间,而不必从内存重新加载它。

嗯,我想这是安全的e、 但是为什么不声明一个函数,将值返回给其他线程,因为它们只读取该值


因为在我看来,将指针传递到单独线程的简单想法已经是一个安全失败。我要告诉你的是:为什么要给出一个(可修改的,公共可访问的)当您只需要值时的整数地址?

假设您在thread func中执行的第一件事是睡眠一秒钟。因此,之后的值将定义为1。

与java中显式启动线程不同,posix线程立即开始执行。
因此,无法保证您在main函数中设置为1的值(假设这是您在伪代码中引用的值)将在线程尝试访问它之前或之后执行。
因此,虽然同时读取整数是安全的,但如果需要写入该值以供线程使用,则需要进行一些同步。
否则,无法保证他们将读取什么值(以便根据您注意到的值采取行动)。
您不应该对多线程进行假设,例如,在访问值之前,每个线程中都有一些处理。

没有任何保证

在任何时刻,您至少应该声明共享变量
volatile
。但是,在任何情况下,您都应该更喜欢其他形式的线程IPC或同步;在这种情况下,它看起来像是您实际需要的。

编辑: Ben是正确的(我说他不是白痴),cpu可能会重新排序指令,并同时沿多条管道执行。这意味着值=1可能会在管道执行“工作”完成之前设置。我的辩护(不是完全的白痴?)我从来没有在现实生活中见过这种情况,我们有一个广泛的线程库,我们运行详尽的长期测试,这种模式一直在使用。如果它发生,我会看到它,但我们的测试从来没有崩溃或产生错误的答案。但是…Ben是正确的,可能性是存在的。它可能一直都在发生代码中的时间,但重新排序没有足够早地设置标志,以使受标志保护的数据的使用者能够在数据完成之前使用数据。我将更改代码以包括屏障,因为无法保证这将继续在野外工作。我相信正确的解决方案与此类似:

读取值的线程:

...
if (value)
{
  __sync_synchronize();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
__sync_synchronize();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
...
if (value)
{
  GENERAL_BARRIER();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
GENERAL_BARRIER();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
设置值的线程:

...
if (value)
{
  __sync_synchronize();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
__sync_synchronize();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
...
if (value)
{
  GENERAL_BARRIER();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
GENERAL_BARRIER();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
尽管如此,我发现这是对障碍的简单解释

编译器屏障 内存障碍会影响CPU。编译器障碍会影响编译器。Volatile不会阻止编译器对代码重新排序。有关详细信息,请参阅

我相信您可以使用此代码防止gcc在编译时重新排列代码:

#define COMPILER_BARRIER() __asm__ __volatile__ ("" ::: "memory")
所以也许这才是真正应该做的

#define GENERAL_BARRIER() do { COMPILER_BARRIER(); __sync_synchronize(); } while(0)
读取值的线程:

...
if (value)
{
  __sync_synchronize();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
__sync_synchronize();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
...
if (value)
{
  GENERAL_BARRIER();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
GENERAL_BARRIER();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
设置值的线程:

...
if (value)
{
  __sync_synchronize();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
__sync_synchronize();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
...
if (value)
{
  GENERAL_BARRIER();  // don't pipeline any of the work until after checking value
  DoSomething();
}
...
...
DoStuff()
GENERAL_BARRIER();  // Don't pipeline "setting value" until after finishing stuff
value = 1;  // Stuff Done
...
使用GENERAL_BARRIER()可以防止gcc对代码进行重新排序,也可以防止cpu对代码进行重新排序。现在,我想知道gcc是否会在其内置的内存屏障上对代码进行重新排序,\u sync\u synchronize(),这会使编译器\u BARRIER的使用变得多余

X86 正如Ben指出的,不同的体系结构在如何重新排列执行管道中的代码方面有不同的规则。Intel似乎相当保守。因此,Intel可能不需要太多的屏障。但这不是避免屏障的好理由,因为这可能会改变

原创帖子: 我们一直这样做。它非常安全(不是所有情况下都安全,但很多情况下都安全)。我们的应用程序在一个巨大的服务器场中的1000台服务器上运行,每台服务器有16个实例,我们没有竞争条件。您应该想知道为什么人们使用互斥锁来保护已经原子化的操作。在许多情况下,锁是浪费时间的。在大多数体系结构上读取和写入32位整数是原子的。不要尝试这样做虽然有32位字段


处理器写重新排序不会影响一个线程读取另一个线程设置的全局值。事实上,使用锁的结果与不使用锁的结果是相同的。如果您赢得比赛并在更改值之前检查值…那么这与赢得锁定值的比赛是一样的,这样在您你读了,功能上是一样的

volatile关键字告诉编译器不要存储val