其他分享
首页 > 其他分享> > c – 使用4个线程获取/释放语义

c – 使用4个线程获取/释放语义

作者:互联网

我目前正在阅读Anthony Williams的C Concurrency in Action.他的一个列表显示了这段代码,他声明z!= 0的断言可以触发.

#include <atomic>
#include <thread>
#include <assert.h>

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x()
{
    x.store(true,std::memory_order_release);
}

void write_y()
{
    y.store(true,std::memory_order_release);
}

void read_x_then_y()
{
    while(!x.load(std::memory_order_acquire));
    if(y.load(std::memory_order_acquire))
        ++z;
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));
    if(x.load(std::memory_order_acquire))
        ++z;
}

int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x);
    std::thread b(write_y);
    std::thread c(read_x_then_y);
    std::thread d(read_y_then_x);
    a.join();
    b.join();
    c.join();
    d.join();
    assert(z.load()!=0);
}

所以我能想到的不同执行路径是这样的:

1)

06001

2)

06002

3)

06003

有人可以向我解释这个断言是如何解雇的吗?

他展示了这个小图:
Image

商店是否也应该与read_x_then_y中的加载同步,而商店要与read_y_then_x中的加载同步?我很困惑.

编辑:

感谢您的回复,我了解原子如何工作以及如何使用获取/发布.我只是没有得到这个具体的例子.我试图弄清楚如果断言触发,那么每个线程做了什么?如果我们使用顺序一致性,为什么断言永远不会触发.

方式,我推理的是,如果线程a(write_x)存储到x,那么它到目前为止所做的所有工作都会与读取x并获取排序的任何其他线程同步.一旦read_x_then_y看到这个,它就会突破循环并读取y.现在,有两件事可能发生.在一个选项中,write_y已写入y,这意味着此版本将与if语句(load)同步,这意味着z递增并且断言无法触发.另一个选项是write_y还没有运行,这意味着if条件失败并且z没有增加,在这种情况下,只有x是真且y仍然是假.一旦write_y运行,read_y_then_x就会跳出它的循环,但x和y都为真,z递增,断言不会触发.我想不出任何“运行”或内存排序,其中z永远不会增加.有人可以解释我的推理存在缺陷吗?

另外,我知道循环读取将始终在if语句读取之前,因为获取会阻止此重新排序.

解决方法:

您正在考虑顺序一致性,最强(和默认)内存顺序.如果使用此内存顺序,则对原子变量的所有访问构成总顺序,并且确实无法触发断言.

但是,在此程序中,使用较弱的内存顺序(释放存储和获取负载).这意味着,根据定义,您不能假定操作的总顺序.特别是,您不能假设更改对于同一订单中的其他线程可见. (对于任何原子内存顺序,只保证每个单独变量的总顺序,包括memory_order_relaxed.)

x和y的存储发生在不同的线程上,它们之间没有同步. x和y的负载出现在不同的线程上,它们之间没有同步.这意味着完全允许线程c看到x&& ! y和线程d看到y&& ! X. (我只是在这里缩写获取 – 加载,不要将此语法用于表示顺序一致的加载.)

结论:一旦你使用比顺序一致的更弱的记忆顺序,你可以亲吻你所有原子的全局状态的概念,这在所有线程之间是一致的,再见.这正是为什么这么多人建议坚持顺序一致性的原因,除非你需要性能(BTW,记得测量它是否更快!)并确定你在做什么.另外,得到第二意见.

现在,你是否会因此被焚烧,这是一个不同的问题.该标准简单地允许断言失败的场景,基于用于描述标准要求的抽象机器.但是,您的编译器和/或CPU可能由于某种原因而无法利用此容差.因此,对于给定的编译器和CPU,您可能永远不会在实践中看到断言被触发.请记住,编译器或CPU可能总是使用比您要求的更严格的内存顺序,因为这永远不会引入违反标准的最低要求.它可能只会花费你一些性能 – 但是标准还没有涵盖这一点.

更新以响应注释:该标准没有定义一个线程看到另一个线程对原子的更改所需的时间的硬上限.有人建议实施者最终应该看到价值观.

有序列保证,但与您的示例相关的保证不会阻止断言触发.基本的获取 – 释放保证是:

>线程e对原子变量x执行释放存储
>线程f从同一原子变量执行获取加载
>然后,如果f读取的值是e存储的值,则e中的存储与f中的负载同步.这意味着e中的任何(原子和非原子)存储在此线程中,在给定存储到x之前排序,对于f中的任何操作都是可见的,在该线程中,在给定的加载之后排序. [请注意,除了这两个以外的线程没有保证!]

因此,不能保证f将读取e存储的值,而不是例如一些较旧的x值.如果它没有读取更新的值,那么加载也不与存储同步,并且对于上面提到的任何相关操作没有排序保证.

我认为原子论的记忆顺序要小于相对论的顺序一致,其中有no global notion of simultaneousness.

PS:也就是说,原子载荷不能只读取任意较旧的值.例如,如果一个线程执行原子< unsigned>的周期性增量(例如,具有释放顺序).变量,初始化为0,另一个线程周期性地从该变量加载(例如,使用获取顺序),然​​后,除了最终换行之外,后一个线程看到的值必须单调递增.但是这遵循给定的排序规则:一旦后一个线程读取5,在从4到5的增量之前发生的任何事情都是在读取5之后的任何事物的相对过去.事实上,除了包装之外的减少是甚至不允许使用memory_order_relaxed,但是这个内存顺序不会对访问其他变量的相对排序(如果有的话)作出任何承诺.

标签:memory-model,memory-barriers,stdatomic,c,multithreading
来源: https://codeday.me/bug/20191004/1853841.html