轮询C程序中其他线程写的变量是否安全?

Thi*_*uda 8 c multithreading pthreads thread-safety compiler-optimization

考虑以下C代码片段:

int flag = 0;
/* Assume that the functions lock_helper, unlock_helper implement enter/leave in
 * a global mutex and thread_start_helper simply runs the function in separate
 * operating-system threads */

void worker1()
{
  /* Long-running job here */
  lock_helper();
  if (!flag)
    flag = 1;
  unlock_helper();
}

void worker2()
{
  /* Another long-running job here */
  lock_helper();
  if (!flag)
    flag = 2;
  unlock_helper();
}


int main(int argc, char **argv)
{
  thread_start_helper(&worker1);
  thread_start_helper(&worker2);
  do
  {
    /* doing something */
  } while (!flag);
  /* do something with 'flag' */
}
Run Code Online (Sandbox Code Playgroud)

问题:

  • 由于某些编译器优化,主标志的'flag'是否总是为0(并且它会在do/while循环中停留)?

  • 'volatile'修饰符会有什么不同吗?

  • 如果答案是'取决于编译器提供的功能',有没有什么办法可以在编译时用配置脚本检查这个'功能'?

cma*_*ter 0

由于您可以假设对齐的加载int是一个原子操作,因此您的代码的唯一危险是优化器:您的编译器可以优化除了flag内部的第一次读取之外的所有内容main(),即将您的代码转换为

int main(int argc, char **argv)
{
  thread_start_helper(&worker1);
  thread_start_helper(&worker2);
  /* doing something */
  if(!flag) {
      while(1) /* doing something */
  }
  //This point is unreachable and the following can be optimized away entirely.
  /* do something with 'flag' */
}
Run Code Online (Sandbox Code Playgroud)

有两种方法可以确保这种情况不会发生:1. 使之成为flag易失性的,这是一个坏主意,因为它包含相当多不需要的开销,2. 引入必要的内存屏障。由于读取 an 的原子性int以及您只想解释flag它更改后的值这一事实,您应该能够在循环条件之前摆脱编译器障碍,如下所示:

int main(int argc, char **argv)
{
  thread_start_helper(&worker1);
  thread_start_helper(&worker2);
  do
  {
    /* doing something */
    barrier();
  } while(!flag)
  /* do something with 'flag' */
}
Run Code Online (Sandbox Code Playgroud)

这里使用的屏障barrier()非常轻,是所有可用屏障中最便宜的。

flag如果您想要分析在引发之前写入的任何其他数据,这还不够,因为您可能仍然从内存加载过时的数据(因为 CPU 决定预取该值)。有关内存栅栏、其必要性及其用途的全面讨论,请参阅https://www.kernel.org/doc/Documentation/memory-barriers.txt

最后,您应该意识到,另一个编写器线程可能会在循环退出flag后随时进行修改do{}while()。因此,您应该立即将其值复制到影子变量,如下所示:

int myFlagCopy;
do
{
  /* doing something */
  barrier();
} while(!(myFlagCopy = flag))
/* do something with 'myFlagCopy' */
Run Code Online (Sandbox Code Playgroud)