GNU C ++中的原子交换

时间:2010-03-19 15:15:53

标签: c++ g++ atomic atomic-swap

我想验证我的理解是否正确。这种事情很棘手,所以我几乎可以肯定我错过了什么。我有一个由实时线程和非实时线程组成的程序。我希望非RT线程能够将指针交换到RT线程使用的内存。

从文档中,我的理解是,这可以在g++中使用:

完成
// global
Data *rt_data;

Data *swap_data(Data *new_data)
{
#ifdef __GNUC__
    // Atomic pointer swap.
    Data *old_d = __sync_lock_test_and_set(&rt_data, new_data);
#else
    // Non-atomic, cross your fingers.                                          
    Data *old_d = rt_data;
    rt_data = new_data;
#endif
    return old_d;
}

这是修改rt_data的程序中唯一的位置(初始设置除外)。在实时上下文中使用rt_data时,会将其复制到本地指针。对于old_d,稍后当确定未使用旧内存时,它将在非RT线程中释放。它是否正确?我需要volatile吗?我应该调用其他同步原语吗?

顺便说一句,我在C ++中这样做,虽然我对C的答案是否不同感兴趣。

提前致谢。

2 个答案:

答案 0 :(得分:25)

volatile中编写并发代码时,通常不要使用C/C++volatile的语义非常接近你想要的它很诱人,但最终volatile是not enough。不幸的是Java/C# volatile != C/C++ volatile。 Herb Sutter有一个很棒的article解释混乱的混乱。

你真正想要的是记忆围栏。 __sync_lock_test_and_set为您提供围栏。

将rt_data指针复制(加载)到本地副本时,还需要一个内存栅栏。

锁定免费编程很棘手。如果你愿意使用Gcc的c ++ 0x扩展,那就更容易了:

#include <cstdatomic>

std::atomic<Data*> rt_data;

Data* swap_data( Data* new_data )
{
   Data* old_data = rt_data.exchange(new_data);
   assert( old_data != new_data );
   return old_data;
}

void use_data( )
{
   Data* local = rt_data.load();
   /* ... */
}

答案 1 :(得分:3)

更新:这个答案不正确,因为我错过了volatile保证不会重新排序对volatile个变量的访问这一事实的事实,但不提供此类保证。尊重其他非volatile访问和操作。内存栅栏确实提供了这样的保证,并且对于该应用程序是必需的。我的原始答案如下,但不要采取行动。请参阅this answer,了解导致以下错误响应的漏洞中的一个很好的解释。

原始答案:

是的,您需要在volatile声明中rt_data; 任何时候可以在访问它的线程的控制流之外修改变量,它应该被声明为volatile 。虽然你可以在没有volatile的情况下离开,因为你正在复制到本地指针,volatile至少有助于文档,并且还会抑制一些可能导致问题的编译器优化。请考虑从DDJ采用的以下示例:

volatile int a;
int b;
a = 1;
b = a;

如果a可能在a=1b=a之间更改其值,则a应声明为volatile(当然,除非,可以接受b的过期值。多线程,尤其是原子基元,构成了这种情况。这种情况也是由信号处理程序和映射到奇数存储器位置的变量(例如硬件I / O寄存器)修改的变量触发的。另请参阅this question

否则,它对我来说很好。

在C中,我可能会使用GLib提供的原子基元。如果原子操作不可用,它们将在可用的情况下使用原子操作,并回退到基于互斥体的缓慢但正确的实现。 Boost可能会为C ++提供类似的功能。