首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >内存屏障与互锁操作

内存屏障与互锁操作
EN

Stack Overflow用户
提问于 2010-07-22 07:57:17
回答 3查看 3.7K关注 0票数 8

我正在努力提高我对记忆障碍的理解。假设我们有一个弱内存模型,并采用了Dekker's algorithm。是否有可能通过添加内存屏障使其在弱内存模型下正常工作?

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

因此,我们可以正确地期望没有弱内存模型系统只提供内存屏障吗?系统必须提供像测试和设置或比较和交换这样的操作才能有用。

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

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2010-07-27 05:24:56

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

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

代码语言:javascript
复制
// initially x=y=z=flag=0

// thread A
x=1;
y=2;
z=3;
release_barrier();
flag=1;

// thread B
while(flag==0) ; // loop until flag is 1
acquire_barrier();
assert(x==1);  // asserts will not fire
assert(y==2);
assert(z==3);

当然,您需要确保对flag的加载和存储是原子的(只要变量适当地对齐,这些简单的加载和存储指令就位于最常见的CPU上)。如果没有线程B上的while循环,线程B可以很好地读取flag的陈旧的值(0),因此您无法保证为其他变量读取的任何值。

因此,栅栏可用于在Dekker算法中强制同步。

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

代码语言:javascript
复制
std::atomic<bool> flag0(false),flag1(false);
std::atomic<int> turn(0);

void p0()
{
    flag0.store(true,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_seq_cst);

    while (flag1.load(std::memory_order_relaxed))
    {
        if (turn.load(std::memory_order_relaxed) != 0)
        {
            flag0.store(false,std::memory_order_relaxed);
            while (turn.load(std::memory_order_relaxed) != 0)
            {
            }
            flag0.store(true,std::memory_order_relaxed);
            std::atomic_thread_fence(std::memory_order_seq_cst);
        }
    }
    std::atomic_thread_fence(std::memory_order_acquire);

    // critical section


    turn.store(1,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release);
    flag0.store(false,std::memory_order_relaxed);
}

void p1()
{
    flag1.store(true,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_seq_cst);

    while (flag0.load(std::memory_order_relaxed))
    {
        if (turn.load(std::memory_order_relaxed) != 1)
        {
            flag1.store(false,std::memory_order_relaxed);
            while (turn.load(std::memory_order_relaxed) != 1)
            {
            }
            flag1.store(true,std::memory_order_relaxed);
            std::atomic_thread_fence(std::memory_order_seq_cst);
        }
    }
    std::atomic_thread_fence(std::memory_order_acquire);

    // critical section


    turn.store(0,std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release);
    flag1.store(false,std::memory_order_relaxed);
}

有关完整的分析,请参阅我的博客文章:http://www.justsoftwaresolutions.co.uk/threading/implementing_dekkers_algorithm_with_fences.html

票数 5
EN

Stack Overflow用户

发布于 2010-07-25 04:00:55

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

http://www.cs.nmsu.edu/~pfeiffer/classes/573/notes/consistency.html

即使在具有弱一致性模型的CPU上,您仍然期望高速缓存一致性。我认为事情脱轨的地方是存储缓冲区和推测读取的行为,以及哪些操作可用于刷新存储写入和使推测读取无效。如果没有可以使推测读取无效的加载围栏,或者没有刷新存储缓冲区的写入围栏,那么除了不能实现Dekker之外,您还不能实现互斥!

这就是我的观点。如果您有一个写屏障和一个读屏障,并且CPU之间的高速缓存是一致的,那么您可以通过在每条指令之后刷新写入(存储围栏),并在每条指令之前刷新推测(读取围栏)来使所有代码按顺序保持一致。所以我声称你不需要原子来做你正在谈论的事情,你可以只用Dekker来做你需要的事情。当然你不会想这么做的。

顺便说一句,Corensic,我工作的公司,编写了调试并发问题的很酷的工具。查看http://www.corensic.com

票数 1
EN

Stack Overflow用户

发布于 2010-07-24 01:07:09

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

您还可以让缓存无效逻辑为您工作。如果您知道由于isync之类的原因,您的加载是当前的,并且如果另一个cpu接触到数据的缓存版本,那么它就会失效,这就足够了吗?

抛开有趣的问题不谈,Dekker的算法在所有实用目的上都是愚蠢的。你会想要在任何实际的应用程序中使用原子硬件接口和内存屏障,所以专注于如何用原子修复Dekker的问题对我来说似乎不值得;)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3304801

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档