代码之家  ›  专栏  ›  技术社区  ›  Jason Kresowaty

内存屏障与联锁操作

  •  8
  • Jason Kresowaty  · 技术社区  · 14 年前

    我正在努力提高我对记忆障碍的理解。假设我们有一个弱记忆模型并且我们适应了 Dekker's algorithm . 有没有可能通过增加记忆障碍使它在弱记忆模式下正常工作?

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

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

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

    3 回复  |  直到 14 年前
        1
  •  5
  •   ildjarn    9 年前

    你是对的,内存障碍不能确保读取看到最新的值。它所做的是在单个线程上和线程之间的操作之间强制执行排序。

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

    // 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可能会为其读取一个过时的值(0) 旗帜

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

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

    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

        2
  •  1
  •   pgod    14 年前

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

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

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

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

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

        3
  •  0
  •   Peeter Joot    14 年前

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

    您还可以使用缓存失效逻辑。如果您知道您的负载是由isync之类的东西造成的,并且如果另一个cpu接触到数据的缓存版本,则该数据的缓存版本将失效,这就足够了吗?