首页 > 系统相关 >C++六种内存序详解

C++六种内存序详解

时间:2024-04-19 15:24:11浏览次数:28  
标签:std C++ 详解 线程 内存 memory 操作 order

前言

要理解C++的六种内存序,我们首先须要明白一点,处理器读取一个数据时,可能从内存中读取,也可能从缓存中读取,还可能从寄存器读取。对于一个写操作,要考虑这个操作的结果传播到其他处理器的速度。 并且,编译器的指令重排和CPU处理器的乱序执行也是我们需要考虑的因素。

 

我们先看一个具体的例子,下图中P1和P2指代不同的processor,假设P2缓存了Data的值
  1. P1 先完成了 Data 在内存上的写操作, Data=2000;
  2. P1 没有等待 Data 的写结果传播到 P2 的缓存中,继续进行 Head 的写操作, Head=1;
  3. P2 读取到了内存中 Head 的新值;
  4. P2 继续执行,读到了缓存中 Data 的旧值, Data=0。

在讲六种内存序之前,先明白两种关系

Happens-before

happens-before 关系是一种逻辑关系,它确保内存操作的有序性和可见性。如果在程序中操作A happens-before操作B,那么操作A的效果(包括对内存的修改)保证对启动操作B的线程可见,并且A的执行在时间上先于B。

Synchronizes-with

synchronizes-with 是C++内存模型中的一个特定类型的 happens-before 关系,它主要用于描述同步机制(如互斥锁、原子操作等)。这种关系指明了程序中的两个操作之间通过某种同步机制直接建立的顺序关系。如果操作A synchronizes-with操作B,则A happens-before B。

例如:

  • 一个线程释放(unlock)一个互斥锁,然后另一个线程获取(lock)这个互斥锁,释放操作与获取操作之间存在synchronizes-with关系。
  • 对原子变量执行store操作(使用memory_order_release或更强的顺序)与另一个线程上对该原子变量进行load操作(使用memory_order_acquire或更强的顺序)之间也存在synchronizes-with关系。

memory_order_relaxed

唯一的要求是在同一线程中,对同一原子变量的访问不可以被重排,不同的原子变量的操作顺序是可以重排的。它不提供任何跨线程的内存顺序保证。

1 int x = 0;
2 int y = 0;
3 // Thread 1:
4 r1 = y.load(std::memory_order_relaxed); // A
5 x.store(r1, std::memory_order_relaxed); // B
6 // Thread 2:
7 r2 = x.load(std::memory_order_relaxed); // C 
8 y.store(42, std::memory_order_relaxed); // D

代码执行后,y1=y2=42的情况是可能出现的,因为第8行的代码可以被重排到第7行之前执行。

memory_order_release & memory_order_acquire & memory_order_consume

Acquire-Release能保证不同线程之间的Synchronizes-With关系,这同时也约束到同一个线程中前后语句的执行顺序。release语句之前的所有变量的读写操作(including non-atomic and relaxed atomic)都对另一个线程中的acquire之后的代码可见。

 1 #include <atomic>
 2 #include <thread>
 3 #include <assert.h>
 4 
 5 std::atomic<bool> x,y;
 6 std::atomic<int> z;
 7 
 8 void write_x_then_y()
 9 {
10     x.store(true,std::memory_order_relaxed);// 1
11     y.store(true,std::memory_order_release);// 2
12 }
13 
14 void read_y_then_x()
15 {
16     while(!y.load(std::memory_order_acquire));// 3
17     if(x.load(std::memory_order_relaxed)) //4
18         ++z;
19 }
20 
21 int main()
22 {
23     x=false;
24     y=false;
25     z=0;
26     std::thread a(write_x_then_y);
27     std::thread b(read_y_then_x);
28     a.join();
29     b.join();
30     assert(z.load()!=0);
31 }

代码执行后assert永远为true. 代码中1一定发生在2之前(happens-before), 2一定发生在3之前(Synchronizes-With), 3一定发生在4之前(happens-before);

而Release-Consume只约束有明确的carry-a-dependency关系的语句的执行顺序,同一个线程中的其他语句的执行先后顺序并不受这个内存模型的影响。release语句之前的有依赖关系的变量的读写操作都对另一个线程中的consume之后的代码可见。

上面的代码的第16行如果从std::memory_order_acquire改成std::memory_order_consume, 最后z是有可能为0的,因为变量x和y之间不存在依赖关系,thread b不一定能看到thread a中的对x的写操作。

根据cppreference,目前memory_order_consume是不建议使用的。

memory_order_acq_rel

它结合了memory_order_acquire 和 memory_order_release 的特性,确保了本线程原子操作的读取时能看到其他线程的写入(acquire 语义),并且本线程的写入对其他线程可见(release 语义),主要用于read-modify-write操作,如fetch_sub/add或compare_exchange_strong/weak。

 1 #include <atomic>
 2 #include <iostream>
 3 #include <thread>
 4 
 5 struct Node {
 6     int data;
 7     Node* next;
 8 };
 9 
10 std::atomic<Node*> head{nullptr};
11 
12 void append(int value) {
13     Node* new_node = new Node{value, nullptr};
14     
15     // 使用 memory_order_acq_rel 来确保对 head 的修改对其他线程可见, 同时确保看到其他线程对 head 的修改
16     Node* old_head = head.exchange(new_node, std::memory_order_acq_rel);
17     
18     new_node->next = old_head;
19 }
20 
21 void print_list() {
22     Node* current = head.load(std::memory_order_acquire);
23     while (current != nullptr) {
24         std::cout << current->data << " ";
25         current = current->next;
26     }
27     std::cout << std::endl;
28 }
29 
30 void thread_func() {
31     for (int i = 0; i < 10; ++i) {
32         append(i);
33     }
34 }
35 
36 int main() {
37     std::thread t1(thread_func);
38     std::thread t2(thread_func);
39     
40     t1.join();
41     t2.join();
42     
43     print_list();
44     
45     return 0;
46 }

memory_order_seq_cst

它满足memory_order_acq_rel的所有特性,除此之外,它强制受影响的内存访问传播到每个CPU核心。它不仅保证了单个原子变量操作的全局顺序,而且保证了所有使用顺序一致性内存序的原子变量之间的操作顺序在所有线程的观测中是一致的。

 

参考文档

https://en.cppreference.com/w/cpp/atomic/memory_order

https://www.codedump.info/post/20191214-cxx11-memory-model-1/

https://www.zhihu.com/question/24301047/answer/83422523

标签:std,C++,详解,线程,内存,memory,操作,order
From: https://www.cnblogs.com/ljmiao/p/18145946

相关文章

  • [转帖]linux命令top内存显示M兆或者G
     转载自:https://zhuanlan.zhihu.com/p/42665560===============许多Linux命令现在都有使其输出更易于理解的选项。让我们了解一些可以让我们心爱的操作系统更友好的东西。不是每个人都以二进制方式思考,他们不想在大脑中给大数字插入逗号来了解文件的大小。因此,Linux命令......
  • [8] UE C++ Mario
    创建了盒子,定义了盒子的碰撞位置能在if里面直接声明赋值局部变量但不能赋值成员变量friend关键词应用导入类的时候如果是灰色就删掉,并且查看头文件 ......
  • RAG 2.0架构详解:构建端到端检索增强生成系统
    关于检索增强生成(RAG)的文章已经有很多了,如果我们能创建出可训练的检索器,或者说整个RAG可以像微调大型语言模型(LLM)那样定制化的话,那肯定能够获得更好的结果。但是当前RAG的问题在于各个子模块之间并没有完全协调,就像一个缝合怪一样,虽然能够工作但各部分并不和谐,所以我们这里介绍RAG......
  • c++函数模板和运行机制
    C++_templatec++提供了函数模板(functiontemplate.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函......
  • c++ double进行精度截取 (转)
    使用boost/multiprecision/cpp_dec_float.hpp #include<boost/multiprecision/cpp_dec_float.hpp>usingboost::multiprecision::number;usingboost::multiprecision::cpp_dec_float_50;intmain(){cpp_dec_float_50v1("5726.867366095");......
  • C++ 类方法解析:内外定义、参数、访问控制与静态方法详解
    C++类方法类方法,也称为成员函数,是属于类的函数。它们用于操作或查询类数据,并封装在类定义中。类方法可以分为两种类型:类内定义方法:直接在类定义内部声明和定义方法。类外定义方法:在类定义内部声明方法,并在类外部单独定义方法。类内定义方法在类定义内部可以直接声明和......
  • 2024.04.18每日收获之联合体结构体内存分配
    今日学习组内前辈留下的代码,数码管动态扫描显示,发现前辈们用的是联合体定义扫描引脚,如:typedefunion{unsignedchara[2];typedefstruct{unsignedchardata0;unsignedchardata1;}data;}seg;此时数组a[2]和结构体里的data0和data1共用地址空间,修改数组或者data会产生相......
  • [10] UE C++ 拓展知识
    SaveGame//--USnakeSaveGame.hUCLASS()classSNAKE_APIUSnakeSaveGame:publicUSaveGame{GENERATED_BODY()public:UPROPERTY()int32GameScore;UPROPERTY()FStringName;UPROPERTY()TArray<int32>Numbers;FStringSlotName;......
  • 详解智慧多功能杆环境监测功能及优势
    环境监测是现代城市管理的重要环节,通过利用各种传感器、监测设备以及网络技术,对城市环境各个方面进行实时监测、分析和管理,不仅可以改善居住环境,保障居民健康,也能响应环境污染事件,促进可持续发展。  智慧多功能杆作为一种融合了物联网、边缘计算等技术的新型多功能基础设施,......
  • JS 中 reduce()方法及使用详解
    reduce()方法可以搞定的东西特别多,就是循环遍历能做的,reduce都可以做,比如数组求和、数组求积、统计数组中元素出现的次数、数组去重等等。reduce()方法对数组中的每个元素执行一个由您提供的reduce函数(依次执行),将其结果汇总为单个返回值。1、语法介绍//arr.reduce(callbac......