在多线程环境中进行单个读取或写入操作的可见性

时间:2011-02-28 04:48:07

标签: multithreading

我有一个与多线程环境中单个读取或写入操作的可见性相关的问题。这是代码(纯C):

volatile int flag = 0; // Global flag

线程A执行:

flag = 1;

线程B检查标志变量:

while (!flag); // Wait for flag

doSomething();

在线程B中读取标志变量是否有任何可见性问题? 我是否需要做任何事情以确保线程B中的标记可见性。就像插入内存屏障一样。

基本上我的问题来自无锁算法。无锁算法基于本地复制概念。 这是我从“AMD多核系统上的无锁编程”文章中得到的:

  

使用CAS进行内存共享的最简单方案是:

     
      
  1. 线程从共享内存加载一个值,在本地存储该值。
  2.   
  3. 然后它对该值进行一些计算,产生一个新值。
  4.   
  5. 最后,它尝试使用CAS更新原始内存位置,同时提交>新值和旧值进行比较。
  6.   
for(;;)
{
    tail = Q->Tail;
    next = tail->next;

    if (tail == Q->Tail)
    {
       if (next == NULL)
       {

         if (CAS( &tail->next, NULL, new_node)) break;
       }
       else
       {
          CAS( &Q->Tail, tail, next);
       }
    }
}

CAS( &Q->Tail, tail, new_node);

所以我想知道当我们做tail = Q->尾巴时我们需要关心的任何可见问题;? 我已经google了很多但仍然困惑,希望得到清晰的理解。

最好的问候,丹尼斯

1 个答案:

答案 0 :(得分:0)

由于您将变量标记为volatile,因此编译器不会尝试将值缓存在寄存器中,以处理您的可见性问题。尽管如此,仔细检查生成的程序集并不会有什么坏处。

您仍应注意编译器或CPU重新排序写入操作。如果dosomething()函数中的代码依赖于另一个线程中发生的写入,则需要使用一些memory barriers来保留指令的顺序。

举个例子:

volatile int flag = 0;
volatile int value = 0;

...

value = 1;
flag = 1;

...

while (!flag);
if (!value) { ... }
dosomething();

这里编译器可以自由地进行以下优化:

flag = 1;
value = 1;

这将在99%的时间内正常工作,但是,每隔一段时间,线程将在两次写入之间中断,这总是会导致“有趣的”调试会话。为避免这种情况,您必须在写入标志之前放置编译器内存屏障。这将向编译器指示在屏障之前必须发生的任何事情。

即便这样做,你仍然有一个错误。很多CPU使用名为Out-of-order execution的东西。虽然您的处理器保证纯顺序代码不会出现问题,但并发代码要求保持一定的读写顺序。顾名思义,CPU没有这样的保证,你最终可能会遇到你试图避免使用编译器障碍的相同情况。解决方法是将CPU内存屏障放置在与编译器内存屏障相同的位置。这将告诉CPU在继续下一条指令之前完成并提交任何挂起的读或写。

请注意,我最近为自己解开了这个问题,所以我希望我能做到这一切。