为什么这个cppreference摘录似乎错误地暗示了原子可以保护关键部分?

江铃汽车
int main() {
    std::vector<int> foo;
    std::atomic<int> bar{0};
    std::mutex mx;
    auto job = [&] {
        int asdf = bar.load();
        // std::lock_guard lg(mx);
        foo.emplace_back(1);
        bar.store(foo.size());
    };
    std::thread t1(job);
    std::thread t2(job);
    t1.join();
    t2.join();
}

显然,这不能保证能正常工作,但可以与互斥锁一起工作。但是,如何用标准的正式定义来解释呢?

考虑以下摘自cppreference的内容:

如果线程A中的原子存储被标记为memory_order_release,并且线程B中来自同一变量的原子加载被标记为memory_order_acquire [与默认原子相同),则所有内存写操作(非原子和宽松原子)均发生在内存之前。从线程A的角度来看,原子存储在线程B中成为可见的副作用。也就是说,一旦原子加载完成,线程B就可以保证看到线程A写入内存的所有内容。

原子装载和存储(具有默认值或指定的特定获取和释放内存顺序)具有上述的获取-释放语义。(互斥锁的锁定和解锁也是如此。)

这种说法的解释可能是,当线程2的加载操作与线程1的存储操作同步时,可以保证观察到所有在存储之前发生的(甚至是非原子的)写操作,例如矢量修改,使得定义明确的。但是几乎每个人都会同意这会导致分段错误,并且如果job函数循环运行其三行,则肯定会这样做。

鉴于标准措辞似乎暗示原子将以某种方式同步,因此什么标准措辞解释了这两种工具之间功能上的明显差异。

我知道何时使用互斥体和原子,并且该示例不起作用,因为实际上没有同步发生。我的问题是如何解释该定义,以使其与现实中的工作方式不矛盾。

内特·艾德雷奇

引用的段落意思是,当B加载A存储的值时,通过观察存储是否发生,还可以确保B在存储之前所做的所有操作也已发生并且可见。

但这并不能告诉您商店实际上尚未发生的任何事情!

我同意,如果线程B中的负载返回1,则可以安全地得出结论,另一个线程已完成存储,因此退出了关键部分,因此B可以安全地使用foo但是,如果两个线程在任何一个存储之前都执行了加载,则两个加载都可能返回0。您的代码甚至不查看加载的值,因此在这种情况下,两个线程可能会一起进入关键部分。

以下代码将是使用原子保护关键部分的一种安全但效率低的方法。它确保A将首先执行关键部分,B将等到A完成后再继续。(显然,如果两个线程都在等待另一个,则您将陷入死锁。)

int main() {
    std::vector<int> foo;
    std::atomic<int> bar{0};
    std::mutex mx;
    auto jobA = [&] {
        foo.emplace_back(1);
        bar.store(foo.size());
    };
    auto jobB = [&] {
        while (bar.load() == 0) /* spin */ ;
        foo.emplace_back(1);
    };

    std::thread t1(jobA);
    std::thread t2(jobB);
    t1.join();
    t2.join();
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

为什么这个C程序错误地输出2的幂?

来自分类Dev

为什么我的python脚本似乎可以正常工作,但为什么却出现500错误?

来自分类Dev

为什么这个缩进是错误的?

来自分类Dev

为什么这个查询是错误的?

来自分类Dev

为什么可以匀称地解析这个“无效的”众所周知的二进制文件?

来自分类Dev

为什么人们似乎暗示我宁愿不使用Boost?

来自分类Dev

为什么这个受保护的属性不起作用?

来自分类Dev

为什么这个函数似乎没有返回任何值

来自分类Dev

为什么你可以做这个转换?

来自分类Dev

为什么此代码似乎存在错误?

来自分类Dev

为什么此代码似乎存在错误?

来自分类Dev

有人可以解释为什么我用sql查询得到这个错误

来自分类Dev

这个 C 程序可以工作,但编译会产生错误。为什么?

来自分类Dev

为什么这个Verilog分配是错误的?

来自分类Dev

为什么这个集合返回此错误

来自分类Dev

为什么这个昏暗的定义是错误的?

来自分类Dev

为什么我得到这个sqlite错误?

来自分类Dev

为什么这个[haskell]编译错误?

来自分类Dev

JSCL-为什么这个神秘的错误?

来自分类Dev

为什么我得到这个分段错误

来自分类Dev

为什么这个java代码显示错误?

来自分类Dev

为什么这个“else”会返回错误?

来自分类Dev

为什么我得到这个错误的输出?

来自分类Dev

为什么这个条件会导致错误?

来自分类Dev

为什么这个函数类型“类型错误”

来自分类Dev

为什么我得到这个错误的输出?

来自分类Dev

为什么ubuntu-bug暗示我不应该报告Compiz和Unity的错误?

来自分类Dev

为什么ubuntu-bug暗示我不应该报告Compiz和Unity的错误?

来自分类Dev

gcc,__ atomic_exchange似乎会产生非原子的asm,为什么?

Related 相关文章

  1. 1

    为什么这个C程序错误地输出2的幂?

  2. 2

    为什么我的python脚本似乎可以正常工作,但为什么却出现500错误?

  3. 3

    为什么这个缩进是错误的?

  4. 4

    为什么这个查询是错误的?

  5. 5

    为什么可以匀称地解析这个“无效的”众所周知的二进制文件?

  6. 6

    为什么人们似乎暗示我宁愿不使用Boost?

  7. 7

    为什么这个受保护的属性不起作用?

  8. 8

    为什么这个函数似乎没有返回任何值

  9. 9

    为什么你可以做这个转换?

  10. 10

    为什么此代码似乎存在错误?

  11. 11

    为什么此代码似乎存在错误?

  12. 12

    有人可以解释为什么我用sql查询得到这个错误

  13. 13

    这个 C 程序可以工作,但编译会产生错误。为什么?

  14. 14

    为什么这个Verilog分配是错误的?

  15. 15

    为什么这个集合返回此错误

  16. 16

    为什么这个昏暗的定义是错误的?

  17. 17

    为什么我得到这个sqlite错误?

  18. 18

    为什么这个[haskell]编译错误?

  19. 19

    JSCL-为什么这个神秘的错误?

  20. 20

    为什么我得到这个分段错误

  21. 21

    为什么这个java代码显示错误?

  22. 22

    为什么这个“else”会返回错误?

  23. 23

    为什么我得到这个错误的输出?

  24. 24

    为什么这个条件会导致错误?

  25. 25

    为什么这个函数类型“类型错误”

  26. 26

    为什么我得到这个错误的输出?

  27. 27

    为什么ubuntu-bug暗示我不应该报告Compiz和Unity的错误?

  28. 28

    为什么ubuntu-bug暗示我不应该报告Compiz和Unity的错误?

  29. 29

    gcc,__ atomic_exchange似乎会产生非原子的asm,为什么?

热门标签

归档