考虑下面的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' */
}
问题:
发布于 2014-01-19 18:26:31
由于可以假定对齐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' */
}
有两种方法可以确保不会发生这种情况: 1.使flag
易失性,这是个坏主意,因为它包含相当多不必要的开销,以及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' */
}
这里使用的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' */
发布于 2014-01-19 18:19:39
代码很可能按原样工作,但有些脆弱。首先,这取决于对flag
的读和写在所使用的处理器上是原子的(并且flag
的对齐就足够了)。
我建议要么使用读锁来读取flag
的值,要么使用任何线程库的功能来使flag
正确原子化。
发布于 2014-01-19 13:15:52
有可能时间在线程之前执行.您必须在使用pthread_join()之前等待线程的执行。
https://stackoverflow.com/questions/21217123
复制相似问题