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'修饰符会有什么不同吗?
如果答案是'取决于编译器提供的功能',有没有什么办法可以在编译时用配置脚本检查这个'功能'?
由于您可以假设对齐的加载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)
| 归档时间: |
|
| 查看次数: |
741 次 |
| 最近记录: |