Concurrency 内存屏障与联锁操作

Concurrency 内存屏障与联锁操作,concurrency,mutex,lock-free,memory-barriers,Concurrency,Mutex,Lock Free,Memory Barriers,我正在努力提高我对记忆障碍的理解。假设我们有一个弱记忆模型,我们适应了它。在弱记忆模型下,通过增加记忆屏障,是否有可能使其正常工作 我认为答案是令人惊讶的否定。原因(如果我是正确的话)是,尽管可以使用内存屏障来确保读取不会经过另一个,但它不能确保读取不会看到过时的数据(例如缓存中的数据)。因此,它可以看到在过去某个时候,关键部分被解锁(根据CPU的缓存),但在当前时间,其他处理器可能会认为它被锁定。如果我的理解是正确的,那么必须使用互锁操作,例如通常称为测试和设置或比较和交换的操作,以确保多个处

我正在努力提高我对记忆障碍的理解。假设我们有一个弱记忆模型,我们适应了它。在弱记忆模型下,通过增加记忆屏障,是否有可能使其正常工作

我认为答案是令人惊讶的否定。原因(如果我是正确的话)是,尽管可以使用内存屏障来确保读取不会经过另一个,但它不能确保读取不会看到过时的数据(例如缓存中的数据)。因此,它可以看到在过去某个时候,关键部分被解锁(根据CPU的缓存),但在当前时间,其他处理器可能会认为它被锁定。如果我的理解是正确的,那么必须使用互锁操作,例如通常称为测试和设置或比较和交换的操作,以确保多个处理器之间内存位置的值同步一致

因此,我们能正确地期望没有弱记忆模型系统只提供记忆屏障吗?系统必须提供测试和设置或比较和交换等操作才能发挥作用

我意识到,包括x86在内的流行处理器提供的内存模型比弱内存模型强得多。请集中讨论弱内存模型

(如果Dekker的算法是一个糟糕的选择,请选择另一种互斥算法,如果可能的话,在这种算法中,内存障碍可以成功地实现正确的同步。)

一些障碍(例如powerpc isync和ia64上的.acq负载)也会对后续负载产生影响。ie:如果由于预取而在isync之前有可用的加载,则必须将其丢弃。如果使用得当,也许这足以使Dekker的算法在弱内存模型上工作

您还可以使用缓存失效逻辑。如果您知道您的负载是当前的,这是由于类似于isync的原因,并且如果另一个cpu接触到数据的缓存版本,则数据的缓存版本将无效,那么这就足够了吗


除了有趣的问题之外,Dekker的算法在所有实际用途上都是愚蠢的。你会想在任何实际应用中使用原子硬件接口和内存屏障,所以专注于如何用原子解决Dekker的问题对我来说似乎不值得;)

假设您在每个语句之后都设置了加载和存储屏障,此外,您还确保编译器不会对存储进行重新排序。在任何合理的架构上,这难道不能提供严格的一致性吗?Dekker致力于顺序一致的体系结构。顺序一致性是比严格一致性弱的条件

即使在一致性模型较弱的CPU上,您仍然希望缓存一致性。我认为事情偏离轨道的地方是存储缓冲区和推测读取的行为,以及可用的刷新存储写入和使推测读取无效的操作。如果没有可以使推测的读取无效的加载围栏,或者没有刷新存储缓冲区的写入围栏,除了无法实现Dekker的之外,您将无法实现互斥

这是我的要求。如果有写屏障和读屏障,并且CPU之间的缓存是一致的,那么可以通过在每条指令之后刷新写(存储围栏),在每条指令之前刷新推测(读围栏),轻松地使所有代码顺序一致。所以我声称,你所谈论的东西不需要原子,你只需要用Dekker的原子就可以做你所需要的。你肯定不想


顺便说一句,我工作的公司Corensic为调试并发问题编写了很酷的工具。检查。

内存屏障不能确保读取看到最新值,这是正确的。它所做的是在操作之间强制排序,无论是在单个线程上还是在线程之间

例如,如果线程A执行一系列存储,然后在最终存储到标志位置之前执行释放屏障,线程B从标志位置读取,然后在读取其他值之前执行获取屏障,那么其他变量将具有线程A存储的值:

//最初x=y=z=flag=0
//穿过
x=1;
y=2;
z=3;
释放_屏障();
flag=1;
//线程B
while(flag==0);//循环直到标志为1
获得障碍物();
断言(x==1);//断言不会被激发
断言(y==2);
断言(z==3);
当然,您需要确保加载和存储到
标志
是原子的(只要变量适当对齐,最常见的CPU上都有简单的加载和存储指令)。如果线程B上没有while循环,线程B可能会读取
标志
的过时值(0),因此无法保证为其他变量读取任何值

因此,在Dekker算法中,围栏可以用来强制同步

这里是C++中的一个示例实现(使用C++ 0x原子变量):

std::原子标记0(假)、标记1(假);
std::原子转动(0);
无效p0()
{
flag0.store(true,std::memory\u order\u released);
标准:原子线程围栏(标准:内存顺序顺序cst);
while(flag1.load(std::memory\u order\u released))
{
如果(旋转加载(标准::内存\u顺序\u松弛)!=0)
{
flag0.store(false,std::内存\u顺序\u松弛);
while(旋转加载(标准::内存\u顺序\u松弛)!=0)
{
}
flag0.store(true,std::memory\u order\u released);
标准:原子线程围栏(标准:内存顺序顺序cst);
}
}
std::原子线程围栏(std::内存顺序获取);
//临界截面
翻转存储(1,标准::内存\u顺序\u松弛);
std::原子线程围栏(std::内存命令释放);
flag0.store(false,std::内存\u顺序\u松弛);
}
无效p1()
{
flag1.store(true,std::memory\u order\u released);
标准:原子线程围栏(标准:内存顺序顺序cst);
while(flag0.load(std::memory\u order\u released))
{
如果(旋转)加载(标准::内存\u顺序\u关系