一步一步写线程之十memory_order的应用

一、并发中的数据处理

在前面已经多次分析过并发及并行编程中的数据处理,特别是同步处理。如果跳出来看这个数据同步的问题,开发者可以看到,在上层使用一些库或者 OS系统提供的同步机制来实现多线程之间数据操作的并发安全。可进一步分析呢?在这些所以的基础调用中,是如何实现的数据同步呢?可以看一下库或者操作系统的源码,比如glibc中使用futex实现。如此抽丝剥茧的分析下去,总会查找到最下面的同步处理机制。
等到找到源头时,才会发现,其实很多都是规则或者说标准,也可以说是协议,当然称做其它也无不可。回到同步,数据的同步其实就是数据处理的规则。而所谓的数据同步其实就是CPU和内存之间的数据的安全传递(后来又出现了多核心和多CPU,这就又涉及到不同CPU间的数据通信)。那么这就涉及到了,内存中如何处理数据。而数据的处理要么按块,要么按流,要么按最小的单元也就是常提到的原子操作。而如果具体到数据的同步,最终就会体现在原子操作上,而对原子操作的同步,就不可避免的需要定义一下处理的规则,这个规则是什么?就是处理原子操作的优先过程,也可以认为是处理的顺序。
举一个简单的例子,军训中的报数,其实就是一个严格次序的过程,一定是从头到尾依次进行,中间不能漏掉也不能任意插入其它人的报数。但是,多个班的报数就可以非严格的按次序进行,只要最终报道都达到约定数量即可。
如果把CPU处理内存数据比做处理报号的过程,其实就可以根据不同的情况来使用不同的调用处理顺序来完成。这在C++中称为memory_order.

二、std::memory_order

在cppreference上英文是这样描述的:

std::memory_order specifies how memory accesses, including regular, non-atomic memory accesses, are to be ordered around an atomic operation. Absent any constraints on a multi-core system, when multiple threads simultaneously read and write to several variables, one thread can observe the values change in an order different from the order another thread wrote them. Indeed, the apparent order of changes can even differ among multiple reader threads. Some similar effects can occur even on uniprocessor systems due to compiler transformations allowed by the memory model.

The default behavior of all atomic operations in the library provides for sequentially consistent ordering (see discussion below). That default can hurt performance, but the library's atomic operations can be given an additional std::memory_order argument to specify the exact constraints, beyond atomicity, that the compiler and processor must enforce for that operation.
Inter-thread synchronization and memory ordering determine how evaluations and side effects of expressions are ordered between different threads of execution. They are defined in the following terms:

说的简单一些,就是std::memory_order和同步是确定线程间按顺序求值的条件。它主要分成以下几种类型:

1、memory_order_relaxed
Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation’s atomicity is guaranteed (see Relaxed ordering below).
宽松内存序,无法保证操作的顺序,只保证原子操作的完整性。
2、memory_order_consume
A load operation with this memory order performs a consume operation on the affected memory location: no reads or writes in the current thread dependent on the value currently loaded can be reordered before this load. Writes to data-dependent variables in other threads that release the same atomic variable are visible in the current thread. On most platforms, this affects compiler optimizations only (see Release-Consume ordering below).

消费内存序(和memory_order_release一起称为释放-消费内存序),它其实就是当前线程的操作(一般是读)依赖于它的前一个写操作。在大多数平台上,这只影响到编译器优化。memory_order_consume 比 memory_order_acquire要稍弱一些,而且基本不推荐使用它。
3、memory_order_acquire
A load operation with this memory order performs the acquire operation on the affected memory location: no reads or writes in the current thread can be reordered before this load. All writes in other threads that release the same atomic variable are visible in the current thread (see Release-Acquire ordering below).
获取内存序(和memory_order_release一起称为获取-释放内存序),即当前线程所有的后续操作必须在当前操作完成后才可以进行。
4、memory_order_release
A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable (see Release-Acquire ordering below) and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic (see Release-Consume ordering below).
释放内存序(和上面两类内存序一起工作 ),即能够保证当前线程中所有的操作完成后才可以执行此操作。
5、memory_order_acq_rel
A read-modify-write operation with this memory order is both an acquire operation and a release operation. No memory reads or writes in the current thread can be reordered before the load, nor after the store. All writes in other threads that release the same atomic variable are visible before the modification and the modification is visible in other threads that acquire the same atomic variable.
它是memory_order_release和memory_order_acquire的组合,即获取-释放内存序。
6、memory_order_seq_cst
A load operation with this memory order performs an acquire operation, a store performs a release operation, and read-modify-write performs both an acquire operation and a release operation, plus a single total order exists in which all threads observe all modifications in the same order (see Sequentially-consistent ordering below).
顺序一致性内存序,最严格的内存序,需要全局保持操作顺序的一致性。在C++的原子库中默认使用的这种内存序,所以,其对执行效率有一定的影响。在一些开源的框架,如Redis中,就可以发现,在某些情况下使用了不同的内存序。

memory_order_acquire和memory_order_consume的不同在于,前者保证对协同使用的 memory_order_release操作之前的所有原子和非原子的写入操作有效,后者只保证协同使用的 memory_order_release操作之前的所有原子的写入操作和依赖原子写入的非原子操作有效。
在X86平台上,基本上这些内存序的处理非常简单,就是几条指令的事。在对memory_order_seq_cst支持中,都有专门的指令,但出于效率考虑,CPU硬件上一般不会使用这种内存序。内存序对编译优化中的指令重排(还有多CPU乱序执行)和CAS(compare_exchange_strong)无锁编程中,是相当关键的。因为它直接决定了使用哪种机制来处理数据。大家可以看下CAS中提供的compare_exchange_strong等操作,它内部就有对内存序的处理参数。

三、原理分析和说明

在计算机中,数据的同步处理其实主要分成两个部分,即数据的交换(Cache和CPU,Cache和Memory,CPU和CPU)和指令重排。这又涉及到两部分,硬件中的内存序和编程语言的内存序,而本次主要说的其实是后者。而前者则和不同的厂家生产的CPU的相关指令有关,如果有兴趣,可以自行查阅Intel、AMD以及ARM相关架构的指令手册。
其实内存序的目的就是为了兼顾效率和安全,严格的使用全局顺序一致,也就是串行一致,则一定会在大多数场景下影响效率。但是这个效率受影响的度,和实际的场景又有关系,所以开发语言对外提供了几种不同的内存序,目的当然是让开发者在知道自己的应用场景下,更好的在安全的前提下,提高处理的效率。
包括前面的指令重排,在多线程的优化中就非常重要。而数据的交换,又必须保证安全,否则交换的意义何在?而通过同步再加上内存序就可以更好的处理一些原子和非原子的操作,从而在底层提供了平衡二者的机制。
另外,一般在开发过程中,有开发者喜欢使用volatile来处理缓存机制导致的一些意外。但在内存序中,是无法保证多线程中这种访问是安全的,或者是原子的,固定排序的。不过,VC是个例外,它可以保证这种顺序,所以其可以在多线程中通过volatile关键字来实现数据的同步。

四、例程

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

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

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

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

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

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

int main()
{
    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); // will never happen
}

其实上面的代码如果换为memory_order_relaxed在ARM架构上还是有可能触发断言的,但在X86的机器上不会。大家可以用多线程的思想和执行的顺序进行思考,即可明白。

五、总结

通过上述的分析和例程测试,可以清理的明白内存模型中内存顺序的意义。通过定义指定的内存序,可以防止编译器的一些特定的操作(指令重排和可见性等),也可以理解为,从标准上屏蔽了对编译器的额外的处理,使得数据在不同平台的不同线程中,只在语言层面就可以控制内存的数据交互。
控制层面向高层发展的结果就是,降低了开发者和设计者的负担,能够更安全的进行多线程的数据编程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web前端开发之CSS_1

CSS选择器字体属性背景属性文本属性表格属性 1. CSS 1.1 CSS简介 CSS&#xff08;Cascading Style Sheets&#xff09;层叠样式表&#xff0c;又叫级联样式表&#xff0c;简称样式表。CSS文件后缀名为 .css 。CSS用于HTML文档中元素样式的定义。使用CSS可以让网页具有美观一致…

算法 || 二分查找

目录 二分查找 在排序数组中查找元素的第一个和最后一个位置 搜索插入位置 一个数组经过划分后具有二段性的都可以用二分查找 二分查找 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; ​ 暴力解法&#xff1a;直接遍历数组&#xff0c;找到 target 便返回下标&am…

【blog项目】layui与jquery冲突导致鼠标悬停事件失效、如何调用layui.use()作用域里的方法

blog项目前台展示——查询数据库中的文章类型并展示时出现的bug 1 正常演示 2 用jquery查询数据库并添加到页面后 3 相关代码 <script src"/static/jquery-2.1.4.js"></script> <script src"/static/layui/layui.js"></script> …

排序算法-计数排序

一、计数排序 这种排序算法 是利用数组下标来确定元素的正确位置的。 如果数组中有20个随机整数&#xff0c;取值范围为0~10&#xff0c;要求用最快的速度把这20个整数从小到大进行排序。 很大的情况下&#xff0c;它的性能甚至快过那些时间复杂度为O(nlogn&#xff09;的排序。…

使用PyCharm开发工具创建工程

一. 简介 前面文章实现了开发 python程序使用的 开发工具PyCharm&#xff0c;本文来学习使用 PyCharm开发工具创建一个 python工程。 二. 使用PyCharm开发工具创建工程 1. 首先&#xff0c;打开 PyCharm开发工具&#xff0c;打开 "New project" 选项&#xff1a; …

git如何查询回退之前的提交记录

git如何查询回退之前的提交记录 使用 git reflog 命令&#xff1a; 使用 git reflog 命令&#xff1a; git refloggit reflog 显示的是你的本地引用日志&#xff0c;它包含了所有HEAD指向变更的历史记录&#xff0c;即使那些已经被删除的提交也会出现在这里。当你误操作回退并…

一款可视化正则表达式工具

regex-vis是一款在线免费且可视化的正则表达式工具 界面图&#xff1a; 只能输入由26个英文字母组成的字符串 ^[A-Za-z]$ 只能输入数字 ^[0-9]*$测试错误 测试正确 快来感受一下叭 官方网址&#xff1a; Regex VisRegex visualizer & editor, make the regular expr…

Java根据模板动态生成Pdf(添加页码、文件加密、Spire免费版本10页之后无法显示问题、嵌入图片添加公章、转Base64)

Java根据模板动态生成Pdf&#xff1a;添加页码、文件加密、Spire免费版本10页之后无法显示问题、嵌入图片添加公章、转Base64 引言【Java根据模板动态生成Pdf资源地址】示例一&#xff1a;动态生成带页码的PDF报告示例二&#xff1a;加密PDF以保护敏感信息示例三&#xff1a;应…

设计模式——终止模式之两阶段终止模式

文章目录 1. 错误思路2. 两阶段终止模式2.1 利用 isInterrupted2.2 利用停止标记interrupt-打断park Two Phase Termination 在一个线程 T1 中如何“优雅”终止线程 T2&#xff1f;这里的【优雅】指的是给 T2 一个料理后事的机会。 1. 错误思路 使用线程对象的 stop() 方法停…

容器工作流

背景 目前某平台使用计算容器和解析容器&#xff0c;这两种容器目前通过rabbitmq消息来进行链接&#xff0c;形成容器工作流&#xff0c;使用容器工作流框架可以省去两个容器中间环节的控制&#xff0c;不需要再使用java代码对容器的操作&#xff0c;通过容器工作流框架即可控…

Docker常见问题排查思路与实战

Docker作为一种流行的容器化技术&#xff0c;已经在众多场景中得到广泛应用。然而&#xff0c;在使用过程中&#xff0c;我们难免会遇到各种问题。本文将介绍一些常见的Docker问题及其排查思路&#xff0c;并通过实战案例帮助大家更好地理解和应对这些挑战。 1. Docker容器启动…

OpenHarmony语言基础类库【@ohos.util.LinkedList (线性容器LinkedList)】

LinkedList底层通过双向链表实现&#xff0c;双向链表的每个节点都包含对前一个元素和后一个元素的引用。当需要查询元素时&#xff0c;可以从头遍历&#xff0c;也可以从尾部遍历&#xff0c;插入、删除效率高&#xff0c;查询效率低。LinkedList允许元素为null。 LinkedList…

数据库和表创建练习

一丶要求 1.创建一个数据库db_classes 2 创建一行表db_hero 3. 将四大名著中的常见人物插入这个英雄表 二丶创建db_classes一个数据库, 使用数据库默认的字符集 create database db_classes; 三丶创建一行表db_hero 1.先切换到我们创建的db_classes;数据库中 use db_class…

RabbitMQ中的交换机类型

交换机类型 可以看到&#xff0c;在订阅模型中&#xff0c;多了一个exchange角色&#xff0c;而且过程略有变化&#xff1a; Publisher&#xff1a;生产者&#xff0c;不再发送消息到队列中&#xff0c;而是发给交换机 Exchange&#xff1a;交换机&#xff0c;一方面&#xff…

03 后端入参校验:自定义注解实现

03 后端入参校验&#xff1a;自定义注解实现 一、前言二、实现1、新建Spring Boot项目2、引入依赖3、新建注解类4、新建校验器5、全局异常处理器6、编写Controller7、新建实体类8、启动并测试 一、前言 在 Java 后端开发中&#xff0c;为了实现入参校验&#xff0c;常常会使用…

【SpringCloud】CircuitBreaker断路器之Resilience4J快速入门

【SpringCloud】CircuitBreaker断路器之Resilience4J快速入门 文章目录 【SpringCloud】CircuitBreaker断路器之Resilience4J快速入门1. 概述2. 服务熔断服务降级(CircuitBreaker)2.1 案例说明2.1.1 基于计数的滑动窗口2.1.2 测试2.2.1 基于时间的滑动窗口2.2.2 测试 3. 隔离(B…

多行Textview 计算切分后的长度,并回退长度

实现类似的效果&#xff0c;一个多行的 textview&#xff0c; 如果赋值一个超长的字符&#xff0c;尾部长度回退部分&#xff0c;并添加 ... 最后添加一个详情按钮。 如果不超长则不显示详情 效果如图&#xff1a; 获取截断之后的字符长度 fun getLimitedCharacterCount(textV…

更新!!!Unity移动端游戏性能优化简谱

UWA官方出品&#xff0c;结合多年优化经验撰写了《Unity移动端游戏性能优化简谱》&#xff0c;文章从Unity移动端游戏优化的一些基础讨论出发&#xff0c;例举和分析了近几年基于Unity开发的移动端游戏项目中最为常见的部分性能问题&#xff0c;并展示了如何使用UWA的性能检测工…

Java web应用性能分析之【6种OOM监控和分析】

Java web应用性能分析之【Linux服务器性能监控分析概叙】-CSDN博客 Java web应用性能分析概叙-CSDN博客 Java web应用性能分析之【基准测试】-CSDN博客 Java web应用性能分析之【sysbench基准测试】-CSDN博客 Java web应用性能分析之【CPU飙升分析概述】-CSDN博客 Java we…

GPT学术优化推荐(gpt_academic )

GPT学术优化 (GPT Academic):支持一键润色、一键中英互译、一键代码解释、chat分析报告生成、PDF论文全文翻译功能、互联网信息聚合GPT等等 ChatGPT/GLM提供图形交互界面&#xff0c;特别优化论文阅读/润色/写作体验&#xff0c;模块化设计&#xff0c;支持自定义快捷按钮&…
最新文章