IPC通过共享内存与atomic_t;它对x86有用吗?

时间:2012-01-02 18:37:55

标签: c++ c linux gcc c++11

我有以下代码通过共享内存进行进程间通信。一个进程写入日志,另一个进程从中读取。一种方法是使用信号量,但在这里我使用的是 atomic_t 类型的原子标志( log_flag ),它位于共享内存中。日志( log_data )也是共享的。

现在的问题是,这适用于x86架构还是需要信号量或互斥量?如果我使 log_flag 非原子怎么办?鉴于x86具有严格的内存模型和主动缓存一致性,并且优化不适用于指针,我认为它仍然有用吗?

编辑:请注意,我有一个8核的多核处理器,所以我在忙等待时没有任何问题!

// Process 1 calls this function
void write_log( void * data, size_t size )
{
    while( *log_flag )
           ;
    memcpy( log_data, data, size );
    *log_flag = 1;
}

// Process 2 calls this function
void read_log( void * data, size_t size )
{
    while( !( *log_flag ) )
       ;
    memcpy( data, log_data, size );
    *log_flag = 0;
}

4 个答案:

答案 0 :(得分:4)

您可能希望在循环中使用以下宏,以避免给内存总线带来压力:

#if defined(__x86_64) || defined(__i386)
#define cpu_relax() __asm__("pause":::"memory")
#else
#define cpu_relax() __asm__("":::"memory")
#endif

此外,它充当内存屏障("memory" param。),因此无需将log_flag声明为volatile

但我觉得这样做太过分了,应该只针对硬实时的东西。你可以使用futex。也许您可以简单地使用管道,它几乎可以用于所有目的。

答案 1 :(得分:2)

我不建议这样做有两个原因:首先,尽管编译器可能无法优化指针访问,但这并不意味着处理器不会缓存指向的值。其次,它是原子的这一事实不会阻止while循环结束与执行* log_flag = 0的行之间的读访问。互斥体更安全,但速度要慢得多。

如果您正在使用pthread,请考虑使用RW互斥锁来保护整个缓冲区,这样您就不需要一个标志来控制它,互斥锁本身就是标志,并且在频繁执行时你会有更好的性能读取。

我也不建议做空的while()循环,你会以这种方式占用所有处理器。在循环中放置一个usleep(1000),让处理器有机会呼吸。

答案 2 :(得分:1)

为什么你应该使用信号量而不是依赖旗帜,有很多原因。

  1. 你的阅读日志while while循环不必要地旋转。这不必要地消耗电力等系统资源。这也意味着CPU不能用于其他任务。
  2. 如果x86完全保证读写顺序,我会感到惊讶。传入数据可能会将日志标志设置为1,仅将传出数据设置为0.这可能意味着您最终会丢失数据。
  3. 我不知道你从哪里获得它的优化不会作为一般用途应用于指针。优化可应用于与外部变化无差异的任何地方。编译器可能不知道并发进程可以更改log_flag。
  4. 问题2可能看起来很可能很少出现并且追踪问题会很困难。所以请帮自己一个忙,并使用正确的操作系统原语。他们将保证事情按预期工作。

答案 3 :(得分:1)

只要log_flag是原子的,你就可以了。

如果log_flag只是一个普通的布尔,你不能保证它会起作用。

编译器可以重新排序指令

*log_flag = 1;
memcpy( log_data, data, size );

只要在log_flag内没有访问memcpy,这在单处理器系统上在语义上是相同的。您唯一的优势可能是劣等优化器,无法推断memcpy中访问的变量。

cpu 可以重新排列说明书 它可以选择在循环之前加载log_flag以优化管道。

缓存可能会重新排序内存写入 包含log_flag的缓存行可能会在包含data的缓存行之前同步到另一个处理器。

你需要的是一种告诉编译器,cpu和缓存“脱手”的方法,这样他们就不会对订单做出假设。这只能用内存栅栏来完成。 std::atomicstd::mutex和信号量都在其代码中嵌入了正确的内存栅栏指令。