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

考虑以下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' */
}

问题:

>对于主线程(和它),’flag’是否总是为0?
由于某些编译器优化,它会陷入do / while循环中?
>’volatile’修饰符会有什么不同吗?
>如果答案是“取决于编译器提供的功能”,那么有没有
我可以使用配置脚本来检查这个“功能”
编译时?

最佳答案
因为你可以假设对齐int的加载是一个原子操作,你的代码唯一的危险就是优化器:你的编译器可以优化除main()中的第一个标志读取,i.即将您的代码转换为

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' */
}

有两种方法可以确保不会发生这种情况:1.使标志易失,这是一个坏主意,因为它包含了相当多的不必要的开销,并且2.引入了必要的内存屏障.由于读取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' */
}

这里使用的屏障()非常轻,它是所有障碍中最便宜的.

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

最后,您应该知道,在do {} while()循环退出后,其他编写器线程可能会随时修改标志.因此,您应该立即将其值复制到阴影变量,如下所示:

int myFlagCopy;
do
{
  /* doing something */
  barrier();
} while(!(myFlagCopy = flag))
/* do something with 'myFlagCopy' */

转载注明原文:轮询C程序中其他线程写的变量是否安全? - 代码日志