我有一个与多线程环境中单个读取或写入操作的可见性相关的问题。这是代码(纯C):
volatile int flag = 0; // Global flag
线程A执行:
flag = 1;
线程B检查标志变量:
while (!flag); // Wait for flag
doSomething();
在线程B中读取标志变量是否有任何可见性问题? 我是否需要做任何事情以确保线程B中的标记可见性。就像插入内存屏障一样。
基本上我的问题来自无锁算法。无锁算法基于本地复制概念。 这是我从“AMD多核系统上的无锁编程”文章中得到的:
使用CAS进行内存共享的最简单方案是:
- 线程从共享内存加载一个值,在本地存储该值。
- 然后它对该值进行一些计算,产生一个新值。
- 最后,它尝试使用CAS更新原始内存位置,同时提交>新值和旧值进行比较。
醇>
for(;;)
{
tail = Q->Tail;
next = tail->next;
if (tail == Q->Tail)
{
if (next == NULL)
{
if (CAS( &tail->next, NULL, new_node)) break;
}
else
{
CAS( &Q->Tail, tail, next);
}
}
}
CAS( &Q->Tail, tail, new_node);
所以我想知道当我们做tail = Q->尾巴时我们需要关心的任何可见问题;? 我已经google了很多但仍然困惑,希望得到清晰的理解。
最好的问候,丹尼斯
答案 0 :(得分:0)
由于您将变量标记为volatile,因此编译器不会尝试将值缓存在寄存器中,以处理您的可见性问题。尽管如此,仔细检查生成的程序集并不会有什么坏处。
您仍应注意编译器或CPU重新排序写入操作。如果dosomething()
函数中的代码依赖于另一个线程中发生的写入,则需要使用一些memory barriers来保留指令的顺序。
举个例子:
volatile int flag = 0;
volatile int value = 0;
...
value = 1;
flag = 1;
...
while (!flag);
if (!value) { ... }
dosomething();
这里编译器可以自由地进行以下优化:
flag = 1;
value = 1;
这将在99%的时间内正常工作,但是,每隔一段时间,线程将在两次写入之间中断,这总是会导致“有趣的”调试会话。为避免这种情况,您必须在写入标志之前放置编译器内存屏障。这将向编译器指示在屏障之前必须发生的任何事情。
即便这样做,你仍然有一个错误。很多CPU使用名为Out-of-order execution的东西。虽然您的处理器保证纯顺序代码不会出现问题,但并发代码要求保持一定的读写顺序。顾名思义,CPU没有这样的保证,你最终可能会遇到你试图避免使用编译器障碍的相同情况。解决方法是将CPU内存屏障放置在与编译器内存屏障相同的位置。这将告诉CPU在继续下一条指令之前完成并提交任何挂起的读或写。
请注意,我最近为自己解开了这个问题,所以我希望我能做到这一切。