Linux上C ++中线程/共享内存之间的线程安全数据交换

时间:2011-10-19 16:37:47

标签: c++ linux multithreading gcc atomic

我有点困惑: 在生产中,我们有两个通过共享内存进行通信的进程,数据交换的一部分是漫长的,一个是bool。对此数据的访问不同步。它已经很好地工作了很长时间,现在仍然如此。我知道修改一个值不是原子的,但考虑到这些值被修改/访问了数百万次,这不得不失败吗?

这是一段代码示例,它在两个线程之间交换一个数字:

#include <pthread.h>
#include <xmmintrin.h>

typedef unsigned long long uint64;
const uint64 ITERATIONS = 500LL * 1000LL * 1000LL;

//volatile uint64 s1 = 0;
//volatile uint64 s2 = 0;
uint64 s1 = 0;
uint64 s2 = 0;

void* run(void*)
{
    register uint64 value = s2;
    while (true)
    {
        while (value == s1)
        {
        _mm_pause();// busy spin
        }
        //value = __sync_add_and_fetch(&s2, 1);
        value = ++s2;
    }
 }

 int main (int argc, char *argv[])
 {
     pthread_t threads[1];
     pthread_create(&threads[0], NULL, run, NULL);

     register uint64 value = s1;
     while (s1 < ITERATIONS)
     {
         while (s2 != value)
         {
        _mm_pause();// busy spin
         }
        //value = __sync_add_and_fetch(&s1, 1);
        value = ++s1;
      }
}

你可以看到我已经评论了几件事:

// volatile uint64 s1 = 0;

// value = __sync_add_and_fetch(&amp; s1,1);

__ sync_add_and_fetch以原子方式递增变量。

我知道这不是很科学,但运行几次没有同步功能它完全没问题。此外,如果我测量同步和没有同步的版本,它们以相同的速度运行,__sync_add_and_fetch如何不添加任何开销?

我的猜测是编译器保证了这些操作的原子性,因此我没有看到生产中的问题。但仍然无法解释为什么__sync_add_and_fetch没有增加任何开销(甚至在调试中运行)。

有关矿山环境的更多细节: ubuntu 10.04,gcc4.4.3 intel i5多核cpu。

生产环境类似于它在更强大的CPU和Centos OS上运行。

感谢您的帮助

3 个答案:

答案 0 :(得分:8)

基本上你在问“为什么我认为

之间的行为/表现没有区别
s2++;

__sync_add_and_fetch(&s2, 1);

好吧,如果你去看看这两种情况下编译器生成的实际代码,你会发现存在差异 - s2++版本将有一个简单的INC指令(或者可能是ADD),而__sync版本将在该指令上有一个LOCK前缀。

那么为什么没有LOCK前缀呢?好吧,虽然通常情况下需要LOCK前缀才能在任何基于x86的系统上运行,但事实证明它不需要它。使用基于Intel Core的芯片,LOCK仅需要通过总线在不同CPU之间进行同步。当在单个CPU上运行时(即使有多个内核),它在没有它的情况下进行内部同步。

那你为什么看不到__sync案件的减速?那么,Core i7是一个“有限”的芯片,因为它只支持单插槽系统,所以你不能拥有多个CPU。这意味着永远不需要LOCK,事实上CPU完全忽略了它。现在代码大1个字节,这意味着如果ifetch或decode有限,它可能会产生影响,但你不是,所以你看不出差异。

如果您要在多插槽Xeon系统上运行,您会看到LOCK前缀的(小)减速,并且还可能在非LOCK版本中看到(罕见)失败。

答案 1 :(得分:1)

我认为编译器在使用某些特定于编译器的模式之前不会产生原子性,所以这是不行的。

如果只有两个进程正在使用共享内存,通常不会出现任何问题,特别是如果代码片段足够短的话。操作系统更喜欢阻塞一个进程并在其最佳时运行另一个进程(例如I / O),因此它将运行一个到一个良好的隔离点,然后切换到下一个进程。

尝试运行同一应用程序的几个实例,看看会发生什么。

答案 2 :(得分:0)

我看到你正在使用Martin Thompson的线程间延迟示例。

  

我的猜测是编译器保证了这些操作的原子性,因此我没有看到生产中的问题。但仍然无法解释为什么__sync_add_and_fetch没有增加任何开销(甚至在调试中运行)。

编译器不保证这里的任何内容。你正在运行的X86平台是。这段代码可能会在时髦的硬件上失败。

不确定你在做什么,但C ++ 11确实提供了std :: atomic的原子性。您还可以查看boost::atomic。我假设您对Disruptor模式感兴趣,我会无耻地将我的端口插入名为disruptor--的C ++。