如何在多个线程中加载之前同步存储? 如果你因为某种原因不能让x成为atomic<>

如何解决如何在多个线程中加载之前同步存储? 如果你因为某种原因不能让x成为atomic<>

考虑以下程序:

#include <thread>
#include <atomic>
#include <cassert>

int x = 0;
std::atomic<int> y = {0};
std::atomic<bool> x_was_zero = {false};
std::atomic<bool> y_was_zero = {false};

void write_x_load_y()
{
    x = 1;
    if (y == 0)
        y_was_zero = true;
}

void write_y_load_x()
{
    y = 1;
    if (x == 0)
        x_was_zero = true;
}

int main()
{
    std::thread a(write_x_load_y);
    std::thread b(write_y_load_x);
    a.join();
    b.join();
    assert(!x_was_zero || !y_was_zero);
}
  1. 鉴于除了访问 x 之外,一切都可以是原子的,我如何保证断言通过?
  2. 如果按原样不可能做到,那么对 x 的访问是否可以是原子的,但不比“放松”强?
  3. 保证这一点所需的最少同步量(例如所有操作的最弱内存模型)是多少?

据我所知,如果没有任何形式的围栏或原子访问,存储 x = 1 可能(如果只是理论上如此)下沉到负载 y == 0 以下(如果本身不是由编译器),从而导致 x 和 y 均为 0 的潜在竞争(并触发该断言)。

我最初的天真印象是 SEQ_CST 保证了非原子变量的完全排序。也就是说,在 SEQ_CST 加载 x 之前排序的 y 的非原子(或宽松)存储保证实际首先发生;类似地,在 y 的非原子(或松弛)加载之前排序的 x 的 SEQ_CST 存储保证实际首先发生;放在一起会阻止比赛。但是,在进一步阅读 https://en.cppreference.com/w/cpp/atomic/memory_order 时,我认为文档实际上并没有说明这一点,而是仅在相反的情况下(在存储之前加载)或同时访问 {{1 }} 和 x 是 SEQ_CST。

同样,我天真地认为内存屏障会强制在屏障之前的所有加载或存储发生在所有加载或存储之前发生,但读取 https://en.cppreference.com/w/cpp/atomic/atomic_thread_fence 似乎暗示它再次仅适用于强制排序屏障前的负载,其后的商店。我认为,这在这里也无济于事,除非我应该在比“商店和货物之间”更不明显的地方设置障碍。

我应该在这里使用什么同步方法?甚至有可能吗?

解决方法

这个想法有致命的缺陷,不可能在 ISO C++ 中使用非原子 x 来保证安全。数据竞争未定义行为 (UB) 是不可避免的,因为一个线程无条件写入 x,另一个线程无条件读取。

充其量你会通过使用编译器屏障来强制一个线程将实际内存状态与抽象机器状态同步,从而滚动你自己的原子。但即便如此,在没有 volatile 的情况下滚动你自己的原子也不是很安全:https://lwn.net/Articles/793253/ 解释了为什么 Linux 内核的手工滚动原子使用 volatile 强制转换为纯存储和纯加载。这在普通编译器上为您提供了类似于放松原子的东西,但当然来自 ISO C++ 的零保证。

When to use volatile with multi threading? 基本上从不——你可以通过使用 atomic<int>mo_relaxed 获得同样高效的 asm。 (或者在 x86 上,甚至在 asm 中获取和释放都是免费的。)

如果您打算尝试这样做,实际上在大多数实现中,std::atomic_thread_fence(std::memory_order_seq_cst) 将阻止编译时跨它的非原子操作的重新排序。 (例如,在 GCC 中,我认为它基本上等同于 x86 asm("mfence" ::: "memory")1,它阻止了编译时重新排序并且也是一个完整的障碍。但我认为其中一些“强度”是一种实现-详细信息,ISO C++ 不要求。

脚注 1:顺便说一句,通常你想要一个带有堆栈内存的虚拟 lock add,而不是真正的 mfence,因为 mfence 更慢。


半相关:您的 bool 变量不需要是原子的。 IDK,如果使它们原子化或多或少会分散注意力;如果他们不是,我倾向于更简单。它们每个都由最多 1 个线程编写,并且只有在该线程被 joined 之后才被读取。您可以将它们设为简单的 bool,也可以根据需要无条件地将它们写成 y_was_zero = (y == 0);。 (但就简单性而言,这是中性的,尽管省去了查看它们的初始值设定项)。


  1. 保证这一点所需的最少同步(例如所有操作的最弱内存模型)是多少?

x 需要是 atomic<> 并且两个商店都需要是 seq_cst。 (这基本上相当于在做完存储后排空存储缓冲区)。

喜欢在https://preshing.com/20120515/memory-reordering-caught-in-the-act/

在实践中,我认为大多数机器上的两种负载都可以是 relaxed(虽然 where private store-forwarding is possible 可能不是 POWER)。 为了保证 ISO C++,我认为您还需要在两个负载上使用 seq_cst ,因此所有 4 个操作都是跨多个对象的全局总操作顺序的一部分,与程序顺序兼容。没有通过 release/acquire 同步来创建一个发生在之前的关系。

通常,seq_cst 是 ISO C++ 内存模型中唯一的排序,它必须转换为基于实际一致状态存在的内存模型中的阻塞 StoreLoad 重新排序,即使没有人在看它,并且个人线程通过本地重新排序访问该状态。 (ISO C++ 只讨论其他线程可以观察到的内容,理论上假设的观察者可能不会约束代码生成。但实际上它们会这样做,因为编译器不进行整个程序的线程间分析。)


如果你因为某种原因不能让x成为atomic<>

使用 C++20 atomic_ref<> 构造对 x 的引用,您可以使用它来执行 xref.store(1,mo_seq_cst)xref.load(mo_seq_cst)

或者使用 GNU C/C++ atomic builtins__atomic_store_n(&x,1,__ATOMIC_SEQ_CST)(这正是 C++20 atomic_ref 旨在包装的内容。)

或者对于半便携的东西,*(volatile int*)&x = 1; 和一个屏障,这可能会也可能不会起作用,这取决于编译器。如果需要,DeathStation 9000 当然可以使 volatile int 赋值非原子化。但幸运的是,人们选择在现实生活中使用的编译器并不可怕,而且通常可用于低级系统编程。尽管如此,这并不能保证任何工作都能奏效。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res