简要描述C++ Memory Order

  现代CPU基本都是多核CPU,基本都具备多线程能力。而涉及到多线程一定会涉及到多线程共享资源数据竞争的问题。如果对竞争资源不加以保护或者针对多线程访问的管理就会出现不同线程读取数据不一致或者更加严重的问题。C++标准库提供了互斥锁(std::mutex)和原子变量(std::atomic)来保证数据在多线程场景下安全的读写。
  互斥锁本身就是让当前线程独占当前代码块来保证线程安全,而原子变量仅仅保护用户定义的原子变量的数据。虽然原子变量只保护原子变量本身,但是我们可以利用原子变量的memory_order来实现相比于互斥锁更加高效的多线程同步。

1 缓存一致性

1.1 缓存一致性

  现代处理器都是多处理器系统,且每个处理器都采用多级缓存策略。每个处理器都有自己的缓存(如L1缓存、L2缓存等),这些缓存是为了提高处理器访问内存数据的速度。然而,当一个处理器对某个内存位置的数据进行了修改,而另一个处理器的缓存中仍然保存着该位置的旧数据时,就会产生数据不一致的问题。缓存一致性就是为了解决这个问题而出现的协议。
  简单的理解就是CPU0将内存中的a(值为x0)加载到自己的L2缓存中读取并修改为x1不会立即写回(频繁写回会有严重的性能问题),此时CPU1从内存中加载x的值并且修改为x2。在两个CPU写回内存时应该以哪个为准,同时CPU0看到的x值的变化序列为x0,x1,而CPU1看到的是x0,x2,不一致。而缓存一致性协议就是为了保障这个顺序的,以防止多线程程序读取错误的值。

在这里插入图片描述

  CPU缓存一致性(Cache Coherence)是指在多处理器系统中,多个处理器共享内存时,各个处理器的缓存中的数据保持一致的特性。这是为了避免一个处理器在其缓存中读取到的数据与另一个处理器写入的主存数据不一致的情况,从而保证程序执行的正确性。假如没有缓存一致性协议保障数据的读取顺序则可能不同线程对竞争数据的修改在不同时刻观测到的结果不同。
  缓存一致性协议种类比较多,比较常见的为MESI协议,该协议保障不同线程读取相同数据的顺序可见性。虽然通常可以通过互斥锁来解决多县策划功能问题,但是互斥锁本身会强制对应代码块的内存序,可能会降低性能。因此通过MESI协议,C++标准支持了原子变量,让开发人员可以控制读取内存的顺序,来尽可能提升内存读写的性能。

1.2 编译器优化

  编译器的优化都是基于运行的程序是单线程的前提,因此编译器和处理器为了优化性能,会对指令进行重排序。这种重排序可能导致多线程程序中的数据竞争和不可预测的行为。不仅仅编译器优化会影响,本身CPU内部也会将进行乱序执行,这也可能影响。

    memory_order简单的描述就是为了解决多核CPU多线程场景下,单线程内指令执行顺序对于多线程的影响。不同的memory_order规定了不同的内存序,可以让我们根据具体的场景进行选择来优化性能。

2 修改顺序

  在具体了解原子变量的memory_order之前先正确的理解下修改顺序。

2.1 修改顺序

  在 C++ 内存模型中,每个变量都有一个单独的修改顺序。修改顺序指的是对该变量的所有修改操作(写操作)按某种全序(total order)排列。这意味着对于每个变量,所有线程都会一致地看到修改的顺序。修改顺序是按以下原则定义的:

  • 全局一致性: 对于每个变量,所有线程看到的修改顺序是一致的。
  • 原子性: 原子操作在修改顺序上是原子的,即对某个变量的原子修改要么完成,要么没有开始,不能处于中间状态。

  不同的内存序对上述语义的支持不同,但是底线是都要保障原子性,也就是说使用原子变量时无论哪种内存序,原子变量一旦被修改,另一个线程读写时其值是可见的。也就是说一个原子变量其修改顺序是对其他线程可见的,如果x从a修改为b再到c,其另一个线程看到的也是该顺序,不可能看到cba。

2.2 Sequenced-Before

  Sequenced-Before用来描述在同一个线程中两个操作之间的顺序关系。具体来说,如果操作 A sequenced-before 操作 B,则表示在程序执行过程中,A 操作的所有副作用在 B 操作开始之前完成。该顺序是由C++的evaluation order决定的,具体可参考Order Of evaluation,简单的理解就是我看看到的代码顺序。
  Sequenced-Before具备传递性,也就是说如果A Sequenced-Before B,B Sequenced-Before C则A Sequenced-Before C。

2.3 Synchronizes-With

  “Synchronizes with”用来描述两个线程之间操作的顺序关系,确保多线程环境下数据的一致性和线程间的正确协调。具体来说,如果一个操作 “synchronizes with” 另一个操作,那么第一个操作的副作用在第二个操作开始之前可见。

  一个线程对原子变量的存储操作可以与另一个线程对同一原子变量的加载操作同步。比如:

std::atomic<int> data(0);
void thread1() {data.store(42, std::memory_order_release);
}void thread2() {int x = data.load(std::memory_order_acquire);std::cout << x;  // 确保看到 x = 42
}

  上面描述的情况是一旦thread1中对data的store操作完成,那么对thread2中的data的读取一定生效,即x一定是42,同时下一行的输出一定也是输出42。如果用relaxed就不会有这种保证,可能出现对指令进行重排序导致data的load操作发生在输出之后。

2.4 Happens-Before

  Happens-Before 是一种用于确定多线程程序中事件顺序的关系。这种关系帮助确定哪些操作在时间上先于其他操作,从而确保程序的正确性和线程间的协调。
  单线程场景下Sequenced-Before可以构成Happens-Before。
  多线程场景下为了明确两个操作之间的Happens-Before关系必须加入一些同步操作,也就是引入Synchronizes-With。通过同步操作可以建立跨线程的Happens-Before关系。
在这里插入图片描述

  比如下面的操作中A Sequenced-Before B,C Sequenced-Before D。而B Sync With C。那么A Happens-Before D。

  但是需要注意的是Happens-Before不代表实际上的执行顺序,CPU和编译器只需要保证在对应的顺序定义上,操作的效果能被后续指令可见即可。比如下面的程序a和b不形成依赖关系,也就是a的求值不依赖b,二者交换顺序不会对程序有额外的副作用,CPU和编译器完全可以针对指令进行排序和合并来提升访存的效率。

void func(){a++;b++;std::cout<<a + 2<<std::endl;
}

3 memory order

3.1 memory_order_seq_cst

  memory_order_seq_cst代表 “sequentially consistent” (顺序一致性)。使用这种内存顺序的原子操作确保了对内存访问的强顺序保证。
  memory_order_seq_cst具备全局顺序一致性 (Sequential Consistency)。所有线程都以相同的顺序观察到所有的原子操作。这意味着如果一个线程观察到一个原子操作的结果,那么其他线程也会以相同的顺序观察到该操作的结果。例如,如果线程 A 执行了一个原子存储操作,然后线程 B 执行了一个原子加载操作,所有其他线程都将以相同的顺序观察到这些操作。
  memory_order_seq_cst获取和释放操作 (Acquire and Release Operations)。加载操作会执行获取操作 (acquire),这意味着加载操作之前的所有操作在当前线程中是可见的。存储操作会执行释放操作 (release),这意味着当前线程的所有操作在存储操作之后对其他线程是可见的。读-改-写操作 (如 fetch_add) 同时执行获取和释放操作,这确保了线程之间的同步。
  单一全局修改顺序 (Single Total Modification Order)。标记为 memory_order_seq_cst 的所有原子操作将被排列在一个全局的修改顺序中。每个线程都会按照这个全局顺序观察到这些原子操作。这意味着在所有线程中,原子操作的顺序是一致的,不会有线程看到不同的操作顺序。

std::atomic<bool> x{false}, y{false};void thread1() {x.store(true, std::memory_order_seq_cst); // (1)
}void thread2() {y.store(true, std::memory_order_seq_cst); // (2)
}std::atomic<int> z{0};void read_x_then_y() {while (!x.load(std::memory_order_seq_cst)); // (3)if (y.load(std::memory_order_seq_cst)) ++z; // (4)
}void read_y_then_x() {while (!y.load(std::memory_order_seq_cst)); // (5)if (x.load(std::memory_order_seq_cst)) ++z; // (6)
}int main() {std::thread a(thread1), b(thread2), c(read_x_then_y), d(read_y_then_x);a.join(), b.join(), c.join(), d.join();assert(z.load() != 0); // (7)
}

  上面的程序有2种情况:

  1. 先执行thrad1再执行thread2,即修改顺序为x->y;
  2. 先执行thrad2再执行thread1,即修改顺序为y->x。

  memory_order_seq_cst能够保证全局唯一的修改顺序,无论哪种顺序至少执行一次z++,即assert不会命中。由于需要向其他核心同步修改顺序,导致这一顺序是原子变量中开销最大的一种。

3.2 acquire-release

  acquire 和 release 是一种现比喻人比于releaxd更强的内存顺序,用于确保数据的同步和可见性。

  • acquire 内存顺序用于加载操作(例如 load 或 fetch),确保该操作不会在它之后的任何操作之前执行。换句话说,它确保当前线程看到的所有加载操作的结果,包括 acquire 操作本身,都不会发生在之前的存储操作之后。
  • release 内存顺序用于存储操作(例如 store 或 exchange),确保该操作不会将任何之前的操作重排序到它之后执行。换句话说,它确保当前线程的所有存储操作都不会发生在之前的加载操作之后。

  简单的说就是A操作在其他线程不能看到发生于releae之后的情况,相对的B操作在其他线程不能看到发生与acquire之前的情况。

在这里插入图片描述

std::atomic<bool> x{false}, y{false};void thread1() {x.store(true, std::memory_order_relaxed); // (1)y.store(true, std::memory_order_release); // (2)
}void thread2() {while (!y.load(std::memory_order_acquire)); // (3)assert(x.load(std::memory_order_relaxed)); // (4)
}

  上面的代码2和3形成sync-with,1 sequenced-before 2,3 sequenced-before 4,即1 三happens-before 4。
  

std::atomic<bool> x{false}, y{false};void thread1() {x.store(true, std::memory_order_seq_cst); // (1)
}void thread2() {y.store(true, std::memory_order_seq_cst); // (2)
}std::atomic<int> z{0};void read_x_then_y() {while (!x.load(std::memory_order_seq_cst)); // (3)if (y.load(std::memory_order_seq_cst)) ++z; // (4)
}void read_y_then_x() {while (!y.load(std::memory_order_seq_cst)); // (5)if (x.load(std::memory_order_seq_cst)) ++z; // (6)
}int main() {std::thread a(thread1), b(thread2), c(read_x_then_y), d(read_y_then_x);a.join(), b.join(), c.join(), d.join();assert(z.load() != 0); // (7)
}

3.3 memory_order_consume

  memory_order_consume旨在比 memory_order_acquire 更具性能优势,特别是对于一些特定的处理器架构。然而,由于目前大多数编译器对 memory_order_consume 的优化支持不足,因此实际上它通常被提升为 memory_order_acquire。
  数据依赖。memory_order_consume 保证的是数据依赖关系。即,如果一个线程执行了一个 memory_order_consume 加载操作,那么所有依赖于这个加载的操作(也就是使用了加载结果的操作)在逻辑上都不能被重排序到这个加载操作之前。更好的性能。memory_order_consume 允许处理器和编译器对不依赖于加载结果的操作进行更多的优化和重排序,从而在某些架构上实现更好的性能。
  consume和acquire的区别是,acquire限制了一整块代码不能重排序,但是阿而consume只是限制了和当前数据有数据依赖的代码不能排序。

3.4 memory_order_relaxed

  memory_order_relaxed与其他内存顺序相比,它提供了最弱的同步保证,但在某些情况下可以显著提高性能。memory_order_relaxed不提供顺序保证,memory_order_relaxed 不会对内存访问进行任何顺序限制。这意味着使用 memory_order_relaxed 的原子操作只保证操作本身的原子性,不保证与其他内存操作的顺序关系。适用于无数据依赖的情况,当多个线程之间的操作不需要顺序保证时,可以使用 memory_order_relaxed 来提高性能。适用于统计计数、日志记录等场景,这些场景中操作的顺序不重要。
  但是x86 架构具有强内存一致性模型,这会影响到使用 memory_order_relaxed 时的实际行为和性能特性。虽然如此releaxd模型也会影响编译器优化,可用于在不需要严格顺序保证的情况下优化并发操作的性能。

std::atomic<bool> x{false}, y{false};void thread1() {x.store(true, std::memory_order_relaxed); // (1)y.store(true, std::memory_order_relaxed); // (2)
}void thread2() {while (!y.load(std::memory_order_relaxed)); // (3)assert(x.load()); // (4)
}

  由于不保证顺序,x和y的存储可能是y->x,因此assert有可能命中。由于1和2没有内存顺序保证因此不构成sequence-before的关系,3和4同理,即1和4不构成happen-before。

4 应用

4.1 自旋锁

  代码不能跨过所以进行重排序,因此使用acquire-release。

class SpinLock {
public:SpinLock() : flag{ATOMIC_FLAG_INIT} {}void lock() {while (flag.test_and_set(std::memory_order_acquire)) {// busy-wait}}void unlock() {flag.clear(std::memory_order_release);}private:std::atomic_flag flag;
};

4.2 引用计数

  比如libcxx中的——shared_ptr的引用技术:

  _LIBCPP_HIDE_FROM_ABI void __add_shared() _NOEXCEPT { __libcpp_atomic_refcount_increment(__shared_owners_); }_LIBCPP_HIDE_FROM_ABI bool __release_shared() _NOEXCEPT {if (__libcpp_atomic_refcount_decrement(__shared_owners_) == -1) {__on_zero_shared();return true;}return false;}
#endif_LIBCPP_HIDE_FROM_ABI long use_count() const _NOEXCEPT { return __libcpp_relaxed_load(&__shared_owners_) + 1; }

5 总结

  memory_order保证原子读写顺序的可见性,C++11的atomic提供了多种内存顺序:

  • memory_order_relaxed: 最宽松的内存顺序, 只保证操作的原子性和修改顺序 (modification order).
  • memory_order_acquire, memory_order_release 和 memory_order_acq_rel: 实现 acquire 操作和 release 操作, 如果 acquire 操作读到了 release 操作写入的值, 或其 release sequence 写入的值, 则构成 synchronizes-with 关系, 进而可以推导出 happens-before 的关系.
  • memory_order_consume: 实现 consume 操作, 能实现数据依赖相关的同步关系. 如果 consume 操作读到了 release 操作写入的值, 或其 release sequence 写入的值, 则构成 dependency-ordered before 的关系, 对于有数据依赖的操作可以进而推导出 happens-before 的关系.
  • memory_order_seq_cst: 加强版的 acquire-release 模型, 除了可以实现 synchronizes-with 关系, 还保证全局顺序一致.

6 参考文献

  • Wiki-Happens-Before
  • 谈谈 C++ 中的内存顺序 (Memory Order)
  • cppreference-memory-order
  • GCC-AtomicSync

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

利用外部知识增强的LEMMA模型:提升多模态虚假信息检测的LVLM方法

LEMMA: Towards LVLM-Enhanced Multimodal Misinformation Detection with External Knowledge Augmentation https://arxiv.org/abs/2402.11943https://arxiv.org/abs/2402.11943 1.概述 多模态虚假信息通过综合文字、图像和视频等多元化形式,在社交平台上的传播过程中,相…

BN的 作用

1、背景&#xff1a; 卷积神经网络的出现&#xff0c;网络参数量大大减低&#xff0c;使得几十层的深层网络成为可能。然而&#xff0c;在残差网络出现之前&#xff0c;网络的加深使得网络训练变得非常不稳定&#xff0c;甚至出现网络长时间不更新或者不收敛的情形&#xff0c;…

牛市中途深度调整,一览下半场值得关注的 Solana 生态五大潜力项目

近期有关加密货币的利空消息让市场行情一度陷入了恐慌之中&#xff0c;短期利空的落地也将伴随着接下来市场的蓄势。对于投资者来说&#xff0c;现在布局超跌潜力项目不失为一个不错的机会。作为本轮牛市值得关注的两大生态&#xff0c;Solana和TON的快速发展和吸金效应&#x…

7个外贸网站模板

Nebula独立站wordpress主题 Nebula奈卜尤拉wordpress主题模板&#xff0c;适合搭建外贸独立站使用的wordpress主题。 https://www.jianzhanpress.com/?p7084 Starling师大林WordPress独立站模板 蓝色橙色风格的WordPress独立站模板&#xff0c;适合做对外贸易的外贸公司搭建…

使用webrtc-streamer查看rtsp实时视频

1.下载webrtc-streamer 2.解压运行webrtc-streamer.exe 在浏览器访问127.0.0.1:8000&#xff0c;点击窗口可以看到本机上各窗口实时状态&#xff0c;点击摄像头可以显示摄像头画面。 5.安装phpstudy&#xff0c;并建立网站。&#xff08;具体过程自己网上搜&#xff09; 6.打开…

给你的博客加上评论区

一个网站如果有评论功能&#xff0c;可以更好的和读者互动。VuePress 也有很多评论插件&#xff0c;这里简单介绍下&#xff0c;最后介绍本站所使用的 Twikoo。 大部分评论插件都是使用的 Github 或 Gitee 的 issue 功能&#xff0c;也就是用 issue 去存储评论&#xff1b;而 …

自定义指令实现Element Plus分页组件内容样式修改

改之前是这样的 改之后是这样的 因为之前我也有写过文章讲解Vue2-ElementUI分页组件的样式修改。 ElementUI 分页组件内容样式修改https://blog.csdn.net/qq_54548545/article/details/139728064且通常情况下&#xff0c;一个项目若是大量使用到分页组件&#xff0c;咱们也不可…

Leetcode104.求二叉树的最大深度

题目描述 递归法 class Solution {public int maxDepth(TreeNode root) {if (root null) { //帮助下面的else语句判空return 0;} else {int leftHeight maxDepth(root.left);int rightHeight maxDepth(root.right);/*** 要注意的点* 1. 这个return是写在else语句里面的&am…

市场营销新手入门:推荐5本让你快速成长的好书!

我过去面试过数千人&#xff0c;发现了一个非常有趣也让人担忧的现象&#xff1a; 无论是资深还是资浅的市场营销人士&#xff0c;如果被问及什么是市场营销&#xff0c;什么是品牌&#xff0c;什么是整合营销传播&#xff0c;市场营销组合与整合营销传播有什么区别&#xff0…

汽车免拆诊断案例 | 奥迪 Q7 e-tron无法通过插电式充电器充电

故障现象 车主反映&#xff0c;车辆无法使用自带的插电式充电器充电。&#xff08;这种充电方法是“Mode 2充电”&#xff0c;3针插头&#xff0c;10 A&#xff0c;2.2 kW&#xff09; 接车后验证故障&#xff0c;将Type 2充电插头连接到车辆时&#xff0c;充电口锁定销循环三…

AD3518 SOP-8封装 单节锂电池保护芯片 可替代XB8608/XB8608A

AD3518 是一款内置 MOSFET 的单节锂电池保护芯片。该芯片具有非常低的功耗和非常低阻抗的内置 MOSFET。该芯片有充电过压&#xff0c;充电过流&#xff0c;放电过压&#xff0c;放电过流&#xff0c;过热&#xff0c;短路&#xff0c;电芯反接等各项保护等功能&#xff0c;确保…

【Superset】dashboard 自定义URL

URL设置 在发布仪表盘&#xff08;dashboard&#xff09;后&#xff0c;可以通过修改看板属性中的SLUG等&#xff0c;生成url 举例&#xff1a; http://localhost:8090/superset/dashboard/test/ 参数设置 以下 URL 参数可用于修改仪表板的呈现方式&#xff1a;此处参考了官…

深入了解Rokid UXR2.0 SDK内置的Unity AR Glass开发组件

本文将了解到Rokid AR开发组件 一、RKCameraRig组件1.脚本属性说明2.如何使用 二、PointableUI组件1.脚本属性说明2.如何使用 三、PointableUICurve组件1.脚本属性说明2.如何使用 四、RKInput组件1.脚本属性说明2.如何使用 五、RKHand组件1.脚本属性说明2.如何使用3.如何禁用手…

产品经理和项目经理,有哪些区别和联系?

产品经理和项目经理在项目管理中扮演着不同的角色&#xff0c;它们之间既有区别又有联系。以下是对两者区别和联系的详细分析&#xff1a; 一、区别 1、工作职责 产品经理&#xff1a;主要负责产品的规划、设计、推广和运营&#xff0c;涵盖了整个产品生命周期的管理。他们需…

华为机试HJ106字符逆序

华为机试HJ106字符逆序 题目&#xff1a; 想法&#xff1a; 将输入的字符串倒叙输出即可 input_str input()print(input_str[::-1])

中小企业有必要使用ERP管理系统?

在激烈市场竞争中&#xff0c;企业共同追求的目的都是——降本增效。大型企业运用ERP系统精细化管理&#xff0c;但对成长中的中小企业&#xff0c;传统ERP投入高昂&#xff0c;难达降本增效之效。中小企业更需要适合其需求的解决方案&#xff0c;所以&#xff0c;相比如传统的…

亚马逊关键词优化全攻略:自养号测评让你的产品跃居首页

常常听到亚马逊运营吐槽&#xff1a; 为啥我的产品就是上不了首页呢&#xff1f; 我的关键词要怎么优化才能排名靠前啊&#xff1f; 的确&#xff0c;每天都有无数个卖家在想方设法让自己的产品排到首页&#xff0c;所以产品的竞争激烈程度不言而喻。 我们在亚马逊运营中&a…

夏季缺血性脑卒中高发,并非毫无征兆

夏日炎炎&#xff0c;脑血管疾病进入了高发期&#xff0c;尤其是缺血性脑卒中&#xff0c;其发病率显著上升。许多人误以为这种疾病来得悄无声息&#xff0c;实则不然&#xff0c;缺血性脑卒中在发作前往往有明确的预警信号。 首先&#xff0c;突发性的剧烈头痛、眩晕、肢体麻木…

Navicat Premium 15 for Mac/Win 中文安装包下载

Navicat Premium 15 是一款数据库管理工具&#xff0c;它支持多种类型的数据库&#xff0c;包括 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite。该软件提供了一个用户友好的图形界面&#xff0c;使得数据库的管理变得更加简单和高效。Navicat Premium 1…

基于Android平台开发,天气预报APP

1.项目功能思维导图 2. 项目涉及到的技术点 数据来源&#xff1a;和风天气API使用okhttp网络请求框架获取api数据使用gson库解析json数据使用RecyclerViewadapter实现未来7天列表展示和天气指数使用PopupMenu 实现弹出选项框使用动画定时器实现欢迎页倒计时和logo动画使用Text…