若依 B 端从浏览器访问升级为 Windows 安装包交付流程
2026/8/6 19:15:01
在单线程程序中,代码书写顺序就是执行顺序。但在多线程并发环境下,尤其是在多核CPU的现代体系结构中,会出现三种重排序:
这些重排序在单线程下透明,但在多线程下可能引发严重问题。
// 经典的双重检查锁定(错误版本)Singleton*Singleton::getInstance(){if(pInstance==nullptr){// 第一次检查lock();if(pInstance==nullptr){// 第二次检查pInstance=newSingleton();// ① 分配内存// ② 调用构造函数// ③ 赋值给指针}unlock();}returnpInstance;}可能的重排序:③ → ②,导致其他线程看到非空指针但对象未完全构造。
为了解决重排序问题,需要告诉编译器和CPU:“这里不能乱序!”。这就是内存屏障或内存顺序的作用。
如果操作 Ahappens-before操作 B,则:
| 内存顺序 | 作用 | 性能成本 | 典型用途 |
|---|---|---|---|
seq_cst | 最强顺序一致性,全局顺序一致 | 最高 | 默认选择,易于推理 |
acq_rel | 获取-释放语义组合 | 中等 | 锁、互斥锁实现 |
acquire | 阻止后续读写重排到它之前 | 低 | 读后数据保护 |
release | 阻止前面读写重排到它之后 | 低 | 写前数据准备 |
consume | 数据依赖排序 | 很低 | 较少使用 |
relaxed | 只保证原子性,无同步 | 最低 | 计数器 |
这种不对称性源于单向信息传递的自然逻辑:
data=42;// ① 准备数据(关键操作)x=10;// ② 其他操作ready.store(true,std::memory_order_release);// ③ 发布标志Release语义:确保在"发送信号"之前的所有操作都已完成且可见。
data = 42重排到ready.store之后[所有之前的操作] | release屏障 | 发布操作ready.store之后的操作可以被重排到前面if(ready.load(std::memory_order_acquire)){// ④ 获取标志use_data(data);// ⑤ 使用数据}Acquire语义:确保在"收到信号"之后的所有操作不会提前执行。
use_data(data)重排到ready.load之前获取操作 | acquire屏障 | [所有之后的操作]ready.load之前的操作可以被重排到后面线程 A(发布者) 线程 B(获取者) ================= ================= 1. data = 42; 2. x = 10; 3. [Release 屏障] ← 创建同步点 → 4. [Acquire 屏障] 4. ready = true; ──────────────→ 5. if (ready) { │ │ 确保可见性───────────────────────────┘ │ │ ↓ ↓ 6. use_data(data); // 保证看到42 }#include<atomic>#include<thread>#include<cassert>std::atomic<int>guard{0};intpayload=0;// 非原子数据voidwriter(){payload=42;// 1. 准备数据guard.store(1,std::memory_order_release);// 2. 发布// Release屏障:确保第1步不会重排到第2步之后}voidreader(){// 可能执行一些不相关的操作inttemp=compute_something();// 等待并获取发布的信息while(guard.load(std::memory_order_acquire)==0){// 忙等待}// Acquire屏障:确保下面的操作不会重排到load之前assert(payload==42);// 3. 安全使用数据// 保证看到writer写入的42}intmain(){std::threadt1(writer);std::threadt2(reader);t1.join();t2.join();return0;}不同架构有不同的实现方式:
; Release store ≈ 普通 store mov [rdi], eax ; Acquire load ≈ 普通 load mov eax, [rdi]; Release store 需要明确屏障 stlr w0, [x1] ; Store-Release ; Acquire load 需要明确屏障 ldar w0, [x1] ; Load-Acquire; Release store stw r3, 0(r4) lwsync ; 轻量级同步指令 ; Acquire load lwz r3, 0(r4) cmpwi r3, 0 isync ; 指令同步Release:打包并寄出包裹
Acquire:收到并拆开包裹
Release:提交代码到主分支
Acquire:从主分支拉取代码
classSpinLock{std::atomic_flag flag=ATOMIC_FLAG_INIT;public:voidlock(){while(flag.test_and_set(std::memory_order_acquire)){// 自旋等待}}voidunlock(){flag.clear(std::memory_order_release);}};std::atomic<bool>initialized{false};std::once_flag flag;SomeType*resource=nullptr;voidlazy_init(){if(!initialized.load(std::memory_order_acquire)){std::call_once(flag,[](){resource=newSomeType();initialized.store(true,std::memory_order_release);});}// 这里可以安全使用resource}// 简化版本,单生产者单消费者template<typenameT>classSPSCQueue{std::atomic<size_t>head{0},tail{0};T*buffer;public:boolpush(constT&item){size_t current_tail=tail.load(std::memory_order_relaxed);// ... 检查队列是否满buffer[current_tail]=item;tail.store(current_tail+1,std::memory_order_release);returntrue;}boolpop(T&item){size_t current_head=head.load(std::memory_order_relaxed);// ... 检查队列是否空item=buffer[current_head];head.store(current_head+1,std::memory_order_release);returntrue;}};默认使用memory_order_seq_cst
理解 Acquire-Release 模式
最小化同步范围
使用高级抽象
std::mutex、std::condition_variable平台差异意识
理解指令排序和内存顺序需要思维模式的转变:
关键要点:
通过合理使用内存顺序,可以在保证正确性的同时最大化并发性能,这是高级并发编程的核心技能之一。