C++六种内存序详解
前言要理解C++的六种内存序,我们起首须要明白一点,处理器读取一个数据时,可能从内存中读取,也可能从缓存中读取,还可能从寄存器读取。对于一个写操纵,要思量这个操纵的效果流传到其他处理器的速度。而且,编译器的指令重排和CPU处理器的乱序实行也是我们需要思量的因素。https://img2024.cnblogs.com/blog/3423623/202404/3423623-20240419135720534-1237205089.png
我们先看一个具体的例子,下图中P1和P2指代差别的processor,假设P2缓存了Data的值https://img2024.cnblogs.com/blog/3423623/202404/3423623-20240419153358198-1884098829.png
[*]P1 先完成了 Data 在内存上的写操纵, Data=2000;
[*]P1 没有等待 Data 的写效果流传到 P2 的缓存中,继续进行 Head 的写操纵, Head=1;
[*]P2 读取到了内存中 Head 的新值;
[*]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是不建议使用的。
https://img2024.cnblogs.com/blog/3423623/202404/3423623-20240419144225759-947430482.png
memory_order_acq_rel
它结合了memory_order_acquire 和 memory_order_release 的特性,确保了本线程原子操纵的读取时能看到其他线程的写入(acquire 语义),而且本线程的写入对其他线程可见(release 语义),主要用于read-modify-write操纵,如fetch_sub/add或compare_exchange_strong/weak。
1 #include2 #include3 #include45 struct Node { 6 int data; 7 Node* next; 8 }; 9 10 std::atomic 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 data next;26 }27 std::cout
页:
[1]