我想验证我的理解是否正确。这种事情很棘手,所以我几乎可以肯定我错过了什么。我有一个由实时线程和非实时线程组成的程序。我希望非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的答案是否不同感兴趣。
提前致谢。
答案 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=1
和b=a
之间更改其值,则a
应声明为volatile
(当然,除非,可以接受b
的过期值。多线程,尤其是原子基元,构成了这种情况。这种情况也是由信号处理程序和映射到奇数存储器位置的变量(例如硬件I / O寄存器)修改的变量触发的。另请参阅this question。
否则,它对我来说很好。
在C中,我可能会使用GLib提供的原子基元。如果原子操作不可用,它们将在可用的情况下使用原子操作,并回退到基于互斥体的缓慢但正确的实现。 Boost可能会为C ++提供类似的功能。