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

时间:2014-01-19 13:06:13

标签: c multithreading thread-safety pthreads 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' */
}

问题:

  • 主线程(和它)的'flag'是否总是为0? 由于某些编译器优化,它会陷入do / while循环中?

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

  • 如果答案是“取决于编译器提供的功能”,那么有没有 我可以使用配置脚本来检查这个“功能” 编译时

3 个答案:

答案 0 :(得分:2)

代码可能按原样运行,但有点脆弱。首先,它取决于对正在使用的处理器上的flag的读取和写入(并且flag的对齐就足够了)。

我建议使用读锁来读取flag的值,或使用您正在使用的任何线程库的功能使flag正确原子。

答案 1 :(得分:0)

有可能在线程之前执行while ...你必须等待执行线程之前,使用pthread_join()

答案 2 :(得分:0)

由于您可以假设对齐int的加载是原子操作,因此您的代码唯一的危险就是优化器:允许您的编译器优化除{{1}的第一次读取之外的所有内容在flag内,我。即将您的代码转换为

main()

有两种方法可以确保不会发生这种情况:1。使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' */ } 易变,这是一个坏主意,因为它包含了相当多的不需要的开销,并且2.介绍必要的记忆障碍。由于读取flag的原子性以及您只想在更改后解释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' */ } 非常轻巧,是所有障碍中最便宜的。

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

最后,您应该知道,在flag循环退出后,其他编写者线程可能会随时修改flag。因此,您应立即将其值复制到阴影变量,如下所示:

do{}while()