C++内存序

news/2025/3/31 7:12:16/文章来源:https://www.cnblogs.com/lang77/p/18797212

《C++ concurrency in action》5.3 Synchronizing operations and enforcing ordering

首先就是对于程序来说都是通过 "happens-before" 和 "synchronizes-with" 来 enforcing ording

synchronizes-with

The basic idea is this: a suitably-tagged atomic write operation, W, on a variable, x, syn
chronizes with a suitably-tagged atomic read operation on x that reads the value stored
by either that write, W, or a subsequent atomic write operation on x by the same thread
that performed the initial write, W, or a sequence of atomic read-modify-write operations
on x (such as fetch_add() or compare_exchange_weak()) by any thread, where the
value read by the first thread in the sequence is the value written by W (see section 5.3.4).

主要的意思就是:一个合适标记的 atomic 写操作 W(比如用 memory_order_release)在变量 x 上,可以和一个合适标记的 atomic 读操作 R(比如用 memory_order_acquire)在 x 上建立同步关系(synchronizes with),前提是这个读操作 R 实际读取的是:

  • 写操作 W 写入的值,或
  • 同一线程后续的 atomic 写入值(写在 W 之后),或
  • 任意线程执行的后续的 read-modify-write 操作写入的值

只有这样才会发生同步,注意 synchronizes-with 只能发生在 atomic operations

happens before

happens befor操作的理解是比较直观的,就是在同一个线程中,对于写在前面的 statements happens before 后面的 statements.

要注意的一个点是,如果是写在同一个 statement 的,那么它的顺序是 unspecified,一个例子是

#include <iostream>void foo(int a,int b){std::cout<<a<<”,”<<b<<std::endl;}int get_num(){static int i=0;return ++i;}int main(){foo(get_num(),get_num());    // calls to get_num() are unordered
}

所以最后的答案可能会输出 "1,2" 或者 "2,1"

而对于 inter-threads 不同程序之间的 happens-before 关系是通过 synchronizes-with 来维护的,并且 happens-before 有传递性,thread A happens-before thread B,并且 thread B happens-before thread C,那么 A happens-before C


理解了程序是通过 happens-before 和 synchronizes-with 来 specifies which operations see the effects of which other operations. 来学习一下对于原子操作的 memory ordering

尽管有6种 memory ordering options(memory_order_relaxed, memory_order_consume, memory_order_acquire, memory
_order_release, memory_order_acq_rel, and memory_order_seq_cst), 但是实际上只有3种 memory models:

  1. sequentially consistent
  2. acquire-release
  3. relaxed

Sequentially consitent

对于顺序一致性模型他的理解是很符合我们的直觉的,就是对于整个程序他会有一个total ordering,就是 all threads
must see the same order of operations.

Any sequentially con
sistent atomic operations done after that load must also appear after the store to other
threads in the system using sequentially consistent atomic operations.

就是说:如果一个线程执行了一个 seq_cst 的 atomic load,这个 load 读到了另一个线程通过 seq_cst atomic store 写入的值,那么:

  • 该线程中在 load 之后发生的所有 seq_cst 原子操作,
  • 在其他线程看来,也都必须发生在那个 store 之后。

这保证了所有线程对 seq_cst 原子操作的观察顺序是一致的 —— 所以叫“顺序一致性”(sequential consistency)。

举个例子就是:

std::atomic<int> x{0}, y{0};// Thread A
x.store(1, std::memory_order_seq_cst);
y.store(2, std::memory_order_seq_cst);// Thread B
int r1 = y.load(std::memory_order_seq_cst);
int r2 = x.load(std::memory_order_seq_cst);
如果 Thread B 看到 r1 == 2(说明看到的是 A 的 store(y, 2)),那么它也必须看到 x == 1,因为:

B 在 load(y) 之后又做了 load(x),而这些都是 seq_cst 的。
所以它的观察顺序中,store(x, 1) 也必须在 load(x) 之前完成。

是 default option, 但是性能影响最大。

Relaxed

对于 Relexed 的理解,在文章中使用了一个场景,将每一个 atomic 变量看成了一个人 + 笔记本,
他做两个操作:

  1. 记录你告诉他的新值,将其写在末尾
  2. 你可以从他那里得到一个值,这个值是他笔记本上的其中一个值,等他告诉你这个值之后,
    后续你得到的值要么是你上一次得到的值要么是在其后面的值

更容易理解的说就是,他会记录当前对每一个人他返回的值,之后你再询问他只会回你在当前值后面得到的值或者是当前值

the note book for the man

对 Anne 来说,她下一次可能得到的值就只可能是 2, 42 或者 67 了,对应的5,10,23...就是这个modification order.

然后如果是 relaxed 的话,有多个 atomic 变量,那就是会有很多个这样的 man,并且你询问他的值的时候,
他不一定会返回给你最新的值

一个例子是这样的:

std::atomic<bool> x,y;
std::atomic<int> z;void write_x_then_y()
{x.store(true,std::memory_order_relaxed);y.store(true,std::memory_order_relaxed);
}
void read_y_then_x()
{while(!y.load(std::memory_order_relaxed));if(x.load(std::memory_order_relaxed)) ++z;
}int main()
{x=false;y=false;z=0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();cout << (z.load()!=0 ? "success" : "reordered") << endl;
}

在 relaxed 的情况下,实际上就是线程之间没有 "synchronizes-with" 关系,所以这里 z 的值是有可能为 0 的,
因为他只有相同线程当中的 happens-before 关系

relaxed

于是是有可能说在 y 被观察到说 = true 的情况下,x = false,因为这就相当于对于 thread a 来说他的确通知了 x
去添加一个 true 的记录,但是 x 可能随机选择一个值,然后并没有将这个 updated 的值返回给你,这是有可能的

Acquire-release

然后就有 "Acquire-release",就是通过 release-acquire 来实现一个线程之间的 synchronizes-with 的关系,
当然按照对应 synchronizes-with 的语义一样:只有对应 read 读到的值是之前提到的3种情况的时候,才会发生同步

理解它的话还是以之前 relaxed 的场景,只是如果是 release store 的情况下,除了会告诉 man 对应要加的数字之外
还会告诉他对应的批次,而对应 acquire load 当然他也是随机给你一个值,但是这次他选择的范围除了在顺序上要>=
上一次告诉你的值之外,他选择的值还必须要是你知道的批次中的值,或者其之后的值,这就是是 release 语义的意思

here’s where the acquire
release semantics kick in: if you tell the man all the batches you know about when you
ask for a value, he’ll look down his list for the last value from any of the batches you
know about and either give you that number or one further down the list.

一个例子就是:

std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{x.store(true,std::memory_order_relaxed);   y.store(true,std::memory_order_release);   
}
void read_y_then_x()
{while(!y.load(std::memory_order_acquire));   if(x.load(std::memory_order_relaxed))     ++z;
}
int main(){x=false;y=false;z=0;std::thread a(write_x_then_y);std::thread b(read_y_then_x);a.join();b.join();assert(z.load()!=0);    
}

整个故事就是首先 a 运行 write_x_then_y 他告诉对应管理 x 的 man,说 “Please write true as part of batch 1 from thread a”,然后和管理 y 的说 “Please write true as the last write of batch 1 from thread a,”

同时,b 会不断的询问 y 一个值和对应的 batch 信息,知道他获得了 true,在这个过程中,他会知道对应的 batch, 也因为他知道
了对应的 batch, 当他询问的时候,他不仅仅是要一个值,他还会附上他知道的 batch 信息 "“Please can I have a value, and by the way I know about batch 1 from thread a" 于是他才可以得到 x 为 true,即使在存储 x 的时候使用的是 relaxed.

这也就是为什么说同步的前提是 read 的值是对应的3种情况的时候才能发生同步,因为只有那样才知道说对应新的 batch,也才能使用
此 batch 信息去获得对应的值

而如果这里 y 的 store 和 load 是 relaxed,就算是获得了 y = true 的情况下,因为没有 batch 信息,他依旧可能获得 x = fase,从而 z = 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/906951.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何快速解决单细胞拟时序分析旧版本Monocle包导致的orderCells过程的报错

当我们整理好一版本的单细胞转录组数据分析代码,并完成一个项目的单细胞转录组数据分析项目。当我们再进行另外一个单细胞转录组数据的项目分析时,突然发现原来跑通的代谢,突然一直报错那是一件很苦恼的问题,想必各位科研党遇到这种问题是不是也有砸电脑的心情。今天主要写…

图床图片上传工具-图传

原文:https://ichochy.com/posts/app/20250325.html时隔多年,又重新出发,从零开始,在 AI 的加持下,再次开发这个图床图片上传工具。爆肝数日,基本完成这个软件现有功能。实现了图片资源的上传、分享、和管理等功能。图传 是一个免费开源的图床图片上传工具。 是一款为 ma…

Linux上利用sunshine串流和虚拟显示器实现副屏扩展

场景Linux + Wayland,尝试将空闲的android平板作为扩展显示屏方法根据 https://www.w568w.eu.org/spacedesk-on-linux.html 中的描述,利用 sunshine + moonlight 串流以及虚拟显示器欺骗实现副屏扩展虚拟显示器查看当前系统记录的显示器设备接口的连接情况,使用命令for p in…

Go红队开发—CLI框架(二)

Go红队开发—CLI命令行工具编写基础学习(二)。目录CLI开发框架cobra 集成库目录规范搭建框架根命令参数添加子命令帮助信息爬虫功能(趁热打铁)Goquery处理响应编码处理收集百度热搜榜爬虫功能所有源码 CLI开发框架 师傅们久等了,为了加快进度,这章节添加了一个爬虫功能,也是…

APP性能测试工具-GT

GT(随身调)是腾讯研发的一款可以用来做App性能测试的工具,可以对APP进行快速的性能测试,检测App的CPU、内存、流量、电量、帧率/流畅度等等、还能开启日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等。 虽然现在该项目已经停止维护了…

一文速通Python并行计算:04 Python多线程编程-多线程同步(上)—基于条件变量、事件和屏障

本文介绍了Python多线程同步的三种机制:条件变量(Condition)、事件(Event)和屏障(Barrier),条件变量指的是线程等待特定条件满足后执行,适用于生产者-消费者模型;Event指的是线程通过事件标志进行同步,适用于线程间简单通信;Barrier指的是多个线程需同步到同一阶段…

docker desktop windows安装

我的机器windows 11 家庭版 下载docker desktop for windows 就直接安装了。安装后打开,遇到了界面转圈圈加载不出来问题,docker engine也是stopped. 病急乱投医,先是说要启用hyper-v,控制面板=》程序和功能里没有发现有hyper-v,一看是家庭版,网上倒是有一个脚本可以在家…

C语言打卡学习第6天(2025.3.25)(补发)

只做了一些有关循环分支函数求值的题,感觉循环函数其实差不多,只有一些细微差别,可能是做的题还不够多或者看运用场景吧

C语言打卡学习第5天(2025.3.24)(补发)

1、把char,getchar,putchar简单看了一下,求ascii值之类的 之类的简单看了一下 2、交换值那一题很奇怪,结果我输出的跟答案要求是一样的,交过去之后显示答案错误,白天的时候问一下

Vulnstack红日靶场通关(持续更新)

带你速通内网渗透相关知识点!!!Vulnstack通关 来源于《内网渗透实战攻略》实战部分 个人是写下自己的笔记 攻击链:探索发现阶段->入侵和感染阶段->攻击和利用阶段->探索感知阶段->传播阶段->持久化和恢复阶段 Windows权限级别前置知识:权限层级 账户类型 权…

Ubuntu 24.04安装MySQL,并且配置外网访问

安装启动更新软件包列表sudo apt update安装MySQL软件包sudo apt install mysql-server启动MySQL服务sudo systemctl start mysql重启命令:systemctl restart mysql配置外网访问 需要修改一个配置 vim /etc/mysql/mysql.conf.d/mysqld.cnf注释掉 这行 配置 bind-address …

2022CCPC Online Contest G - Name the Puppy

对正串和反串分别建立 Trie 树,定义 \(dp[i][j]\) 表示正串 Trie 树上编号为 \(i\) 的点匹配反串 Trie 树上编号为 \(j\) 的点所能拼出最长 anti-border 的长度。 如此,从根节点开始搜索,直到无法匹配为止都可以搜,搜到底后回到根节点继续匹配,可以证明,拼出来的 anti-bo…