19

我想验证我的理解是否正确。这种事情很棘手,所以我几乎可以肯定我错过了一些东西。我有一个由实时线程和非实时线程组成的程序。我希望非 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 的答案是否不同感兴趣。

提前谢谢。

4

2 回答 2

26

volatileC/C++. _ 的语义volatile非常接近你想要的,它很诱人,但最终 volatile 是不够的。不幸的是Java/C# volatile != C/C++ volatile。Herb Sutter 有一篇很棒的文章解释了混乱的混乱。

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

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

Lock free programming is tricky. If you're willing to use Gcc's c++0x extensions, it's a bit easier:

#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();
   /* ... */
}
于 2010-03-19T16:03:06.243 回答
3

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

原答案:

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

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

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

否则,我觉得很好。

在 C 语言中,我可能会为此使用GLib提供的原子原语。他们将在可用的情况下使用原子操作,如果原子操作不可用,则退回到基于互斥锁的缓慢但正确的实现。Boost 可能会为 C++ 提供类似的东西。

于 2010-03-19T15:33:24.483 回答