linux上c++线程间的线程安全数据交换/共享内存

thread safe data exchange between threads/shared memory in C++ on linux

本文关键字:线程 交换 共享 内存 数据 c++ linux 安全      更新时间:2023-10-16

我有点困惑:在生产中,我们有两个进程通过共享内存进行通信,数据交换的一部分是long类型和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;

//值= __sync_add_and_fetch(及s1, 1),

__sync_add_and_fetch自动递增一个变量

我知道这不是很科学,但是在没有同步功能的情况下运行几次,它完全可以工作。此外,如果我测量两个版本同步和不同步,他们运行在相同的速度,为什么__sync_add_and_fetch不增加任何开销?

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

关于矿山环境的更多细节:Ubuntu 10.04, gcc4.4.3Intel i5多核cpu。

生产环境是类似的,只是运行在更强大的CPU和Centos操作系统上。

谢谢你的帮助

基本上你是在问"为什么我看不出

s2++;

__sync_add_and_fetch(&s2, 1);

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

那么为什么没有LOCK前缀也可以工作呢?虽然通常情况下,LOCK前缀对于任何基于x86的系统都是必需的,但对于您的系统却不需要。对于基于Intel Core的芯片,LOCK只需要在总线上的不同cpu之间进行同步。当在单个CPU上运行时(即使有多个内核),它在没有CPU的情况下进行内部同步。

那么为什么在__sync的情况下你没有看到放缓?好吧,酷睿i7是一个"有限"的芯片,因为它只支持单插槽系统,所以你不能有多个cpu。这意味着永远不需要LOCK,实际上CPU完全忽略了它。现在代码变大了1字节,这意味着如果你的读取或解码受到限制,它可能会产生影响,但你没有,所以你看不出有什么不同。

如果您在多套接字Xeon系统上运行,您将看到LOCK前缀的(小)减速,并且在非LOCK版本中也可能看到(罕见的)失败。

我认为编译器不会产生原子性,除非你使用一些特定于编译器的模式,所以这是不允许的。

如果只有两个进程在使用共享内存,通常不会发生问题,特别是如果代码段足够短。操作系统倾向于阻塞一个进程,并在最佳情况下运行另一个进程(例如I/O),因此它将运行一个进程到一个良好的隔离点,然后切换到下一个。

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

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

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

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

不知道你在做什么,但是c++ 11确实通过std::atomic提供了原子性。您还可以查看boost::atomic。我假设您对Disruptor模式感兴趣,我将无耻地将我的移植到c++中称为Disruptor——.