可以安全地在线程之间共享整数吗?

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

为了简化问题,

  • 只有一个线程会写入整数
  • 多个线程将读取整数

这个伪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; 

我认为它是安全的,因为一个整数占用一个处理器字,读取/写入一个字应该是最primefaces的操作,对吧?

你的伪代码不安全。

虽然访问一个字大小的整数确实是primefaces的,这意味着你永远不会看到一个中间值,但是“在写入之前”或“在写入之后”,这对于你概述的算法来说是不够的。

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

您需要某种内存栅栏来防止写入重新排序。 否则,不能保证其他线程能够看到新值。

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

我不会指望它。 编译器可以发出代码,假定它在CPU寄存器中的任何给定时间知道’value’的值是什么,而无需从内存重新加载它。

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

读取值的线程:

 ... 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 ... 

话虽如此,我发现这是对障碍的简单解释。

编译器障碍内存障碍会影响CPU。 编译器障碍会影响编译器。 易失性不会阻止编译器重新排序代码。 这里有更多信息。

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

 #define COMPILER_BARRIER() __asm__ __volatile__ ("" ::: "memory") 

那么也许这应该是真正应该做的?

 #define GENERAL_BARRIER() do { COMPILER_BARRIER(); __sync_synchronize(); } while(0) 

读取值的线程:

 ... 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是否不会通过内存,__sync_synchronize()重新排序代码,这将使COMPILER_BARRIER的使用变得多余。

X86正如Ben所指出的,不同的体系结构对于如何在执行流水线中重新排列代码有不同的规则。 英特尔似乎相当保守。 因此,英特尔可能不需要几乎同样的障碍。 虽然这可能会改变,但这并不是避免障碍的好理由。

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

处理器写入重新排序不会影响一个线程读取另一个线程设置的全局值。 实际上,使用锁的结果与没有锁的结果相同。 如果你赢了比赛并在改变之前检查它……那就像赢得比赛一样锁定价值所以没有其他人可以在你阅读时改变它。 function相同。

volatile关键字告诉编译器不要在寄存器中存储值,而是要继续引用原始内存位置。 除非您正在优化代码,否则这应该没有任何效果。 我们发现编译器对此非常聪明,并且还没有遇到volatile变化的情况。 编译器似乎非常擅长提出寄存器优化的候选者。 我怀疑const关键字可能会鼓励对变量进行寄存器优化。

如果编译器知道最终结果不会有不同,则编译器可能会对函数中的代码进行重新排序。 我没有看到编译器使用全局变量执行此操作,因为编译器不知道如何更改全局变量的顺序将影响立即函数之外的代码。

如果某个function正在运行,您可以使用__attrribute__在function级别控制优化级别。

现在,也就是说,如果您使用该标志作为网关,只允许一个组的一个线程执行某些工作, 那就不行了 。 示例:线程A和线程B都可以读取标志。 线程A被安排出去。 线程B将标志设置为1并开始工作。 线程A唤醒并将标志设置为1并开始工作。 哎呀! 为了避免锁定并仍然执行类似的操作,您需要查看primefaces操作,特别是像__sync_bool_compare_and_swap(value,old,new)这样的gccprimefaces内置函数。 如果值当前为旧,则允许您设置value = new。 在前面的示例中,如果value = 1,则只有一个线程(A或B)可以执行__sync_bool_compare_and_swap(&value,1,2)并将值从1更改为2.丢失的线程将失败。 __sync_bool_compare_and_swap返回操作成功。

在内心深处,使用primefaces内置函数时会有一个“锁定”,但与使用互斥锁相比,它是一个硬件指令并且非常快。

也就是说,当您必须同时更改大量值时,请使用互斥锁。 primefaces操作(截至今日)仅在必须以primefaces方式更改的所有数据都可以适合连续的8,16,32,64或128位时才起作用。

假设你在hibernate一秒钟时在线程函数中做的第一件事。 所以那之后的价值肯定是1。

在任何时刻,您至少应该声明共享变量volatile 。 但是,在所有情况下,您都应该更喜欢其他forms的线程IPC或同步; 在这种情况下,它看起来像一个条件变量是你真正需要的。

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

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