惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security @ Cisco Blogs
罗磊的独立博客
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
T
The Blog of Author Tim Ferriss
美团技术团队
T
Tailwind CSS Blog
博客园 - 三生石上(FineUI控件)
博客园 - Franky
G
Google Developers Blog
Jina AI
Jina AI
Stack Overflow Blog
Stack Overflow Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
Visual Studio Blog
腾讯CDC
S
SegmentFault 最新的问题
Recent Announcements
Recent Announcements
博客园 - 叶小钗
Microsoft Security Blog
Microsoft Security Blog
雷峰网
雷峰网
L
LangChain Blog
Vercel News
Vercel News
Forbes - Security
Forbes - Security
PCI Perspectives
PCI Perspectives
N
News | PayPal Newsroom
S
Security Affairs
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
J
Java Code Geeks
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Hacker News: Ask HN
Hacker News: Ask HN
Schneier on Security
Schneier on Security
A
About on SuperTechFans
Attack and Defense Labs
Attack and Defense Labs
Google Online Security Blog
Google Online Security Blog
aimingoo的专栏
aimingoo的专栏
MongoDB | Blog
MongoDB | Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
Cloudbric
Cloudbric
B
Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Proofpoint News Feed
D
DataBreaches.Net
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
N
News and Events Feed by Topic

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 分享一下笔者的 Mac 装机必备软件 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
并发编程核心概念辨析
同勉共进 · 2026-04-19 · via 博客园_首页

本文旨在辨析并发编发中的常见核心概念,目的是防止初学者在学习过程中对相关概念一知半解,互相混淆,越学越懵。本文以澄清概念为主,对部分知识点,比如 MESI 缓存一致性协议,不会深入介绍,感兴趣的读者请自行学习。

一、背景:CPU 多级缓存架构

为了读者在阅读后序章节时,有更清晰、更形象的认知,这里放上现代 CPU 缓存的典型结构。一图胜千言,不多赘述,仅陈述以下要点:

  • 一颗 CPU,往往有多个核心(core)。同一时刻,每个 core 上都可以运行一个线程(thread)。
  • 为了追求更高的效率:
    • 编译器 / CPU 可能重排某些指令,把后面的提前执行。
    • CPU 增加了多级缓存。

这使得某些情况下,某个线程看到的数据,可能是非预期的,进而导致程序出现逻辑错误。

当然,这并不是说编译器或者 CPU 的设计有缺陷,而是一种平衡与妥协:为了效率, 编译器 / CPU 会适度“放宽政策”,不做过于严苛的约束和检查,这在多数情况下是安全的;为了正确性,在并发编程时,开发者需要更加精细的干预。

🔍 点击查看图片 CPU cache

二、概念辨析

1. 缓存一致性(Cache Coherence)

层次:纯硬件机制,在 CPU 内部固化。

解决的问题同一个内存地址(单一变量)多个 CPU 核心的本地缓存(L1/L2 Cache)中如何同步。即,一个核心的写入如何让其它核心感知?

核心0的L1缓存:  [addr X] = 1  ← 我刚写入
核心1的L1缓存:  [addr X] = 0  ← 这里还是旧值,怎么同步?

机制:MESI、MOESI、MESIF协议(状态机)。注意,这些缓存一致性协议是固化在CPU内部的,纯硬件实现。

M (Modified)  - 该 cache line 仅存在当前 cache 中,与内存不一致(dirty),其他 CPU core 缓存无效
E (Exclusive) - 该 cache line 仅存在当前 cache 中,与内存一致(clean)
S (Shared)    - 该 cache line 被多核共享,且与内存一致
I (Invalid)   - 该 cache line 已失效,需重新加载

关键特性

  • 地址/变量
  • 最终一致,但不保证何时能看到(因为有 store buffer)。
  • 不保证多个地址之间的对外可见顺序(核心1可能先看到先看到修改后的变量A,后看到修改后的变量B;核心2可能正好赶过来)。
  • 对程序员完全透明,你不需要也不能直接操控它。

通俗理解:它是底层的“群消息同步机制”,保证群里所有人看到的单条消息内容是一致的。

2. 内存一致性模型(Memory Consistency Model)

层次:硬件架构规范层,由 CPU 架构决定,不同类型的 CPU 不一样。

解决的问题:多个地址上的读写操作,从其它核心观察时,顺序会不会乱?允许哪些重排?

这才是真正决定"多线程程序正确性"游戏规则的模型:

                                                  允许的重排
模型                           Load-Load   Load-Store  Store-Store  Store-Load
───────────────────────────────────────────────────────────────────────────────────────
Sequential Consistency (SC)        ×           ×            ×           ×
Total Store Order (TSO)            ×           ×            ×           ✓  ← 只允许这个
Relaxed / Weak Consistency         ✓           ✓           ✓           ✓

不同架构的选择

  • x86/x64:TSO(Total Store Order)——接近最强,只允许 Store→Load 重排。
  • ARM:非常宽松(weakly ordered),几乎允许所有重排。
  • POWER:类似 ARM,甚至更宽松。

缓存一致性 vs 内存一致性模型的区别

  • 缓存一致性回答:一个地址的写入最终会传播吗?答:会(最终所有核心一定能看到一致结果)
  • 内存一致性模型回答:多个地址的操作,以什么顺序传播?是否允许后操作的先被看到?

通俗理解:你发了消息 A,又发了消息 B。内存一致性模型决定了,群里其他人有没有可能先看到 B,后看到 A。

3. 内存屏障(Memory Barrier / Fence)

层次:硬件指令 + 编译器指令

解决的问题:在代码的特定位置,强制约束重排序边界。是程序员/编译器用来干预“内存一致性”的物理武器。

类型

  • Load Barrier:屏障前的所有 load 操作,必须在屏障后的任何 load 操作开始之前,全局完成(对其他处理器可见)。
  • Store Barrier:屏障前的所有 store 操作,必须在屏障后的任何 store 操作开始之前,全局完成(从 Store Buffer 刷新到 L1 Cache,并对其他处理器可见)
  • Full Barrier: 屏障前的所有读和写操作,必须在屏障后的任何读和写操作开始之前,全局完成。

具体指令

x86:
  LFENCE  → Load Barrier
  SFENCE  → Store Barrier  
  MFENCE  → Full Barrier(最常用)
  LOCK前缀 → 隐含 Full Barrier

ARM:
  DMB ISH   → Full Barrier(数据内存屏障)
  DSB ISH   → 更强的同步屏障
  ISB       → 指令同步屏障(刷流水线)

两种屏障(注意区分):

// 编译器屏障(只防止编译器重排,CPU不受限)
asm volatile("" ::: "memory");          // GCC
_ReadWriteBarrier();                    // MSVC

// 硬件屏障(同时防止编译器重排 + CPU重排)
asm volatile("mfence" ::: "memory");   // x86 Full Barrier

4. 内存序(Memory Order)

层次:C++ 编程语言层,C++11 引入。

本质:是对内存屏障的高级抽象,让程序员用语义而非汇编指令来表达需求。

机制:你写下memory_order,编译器会根据当前的 CPU 架构(x86 还是 ARM),自动帮你翻译成对应架构的内存屏障指令(Memory Barrier)。

六个级别

relaxed    → 只保证原子性,不产生任何屏障
acquire    → Load 时用:屏障后的操作不能重排到此 Load 之前
release    → Store 时用:屏障前的操作不能重排到此 Store 之后
acq_rel    → 用于 RMW:同时具备 acquire + release 语义
consume    → acquire 的弱化版(实践中几乎不用)
seq_cst    → 最强:全局顺序一致,等价于 Full Barrier

编译器如何翻译

C++ memory_order          x86 生成          ARM 生成
─────────────────────────────────────────────────────
relaxed load          →   MOV              LDR
relaxed store         →   MOV              STR
acquire load          →   MOV              LDAR
release store         →   MOV              STLR
seq_cst store         →   MOV + MFENCE     STLR + DMB
seq_cst load          →   MOV              LDAR

x86 上 acquire/release 不需要额外指令(因为 TSO 已经提供了大部分保证),ARM 上需要专用指令。

5. 四者关系

🔍 点击查看图片 relationship

它们的依赖关系

  • 缓存一致性:硬件底座,没有它,写入根本无法传播,其他一切无从谈起。
  • 内存一致性模型:硬件架构契约与规则,定义了默认允许什么、禁止什么。
  • 内存屏障:硬件指令,是工具和手段:当默认规则不够用时,用它来强化约束。
  • 内存序:是软件层高级抽象,C++ 程序员通过它告诉编译器需要什么保证

三、内存序/内存屏障的作用范围

作用1:防止当前线程内的指令重排(编译器 + CPU)

// 没有屏障,编译器和CPU可能重排这两条指令
data = 42;          // 可能被移到 flag store 之后!
flag = true;

// 有 release 屏障,data = 42 一定在 flag = true 之前完成
data = 42;
flag.store(true, memory_order_release);  // 屏障

作用2:控制跨线程的可见性时序

通过控制“store 何时变得全局可见”和“load 何时看到最新值”来影响其他线程的观察结果

内存屏障的物理效果(以 x86 TSO 为例):

CPU Core 0:                    Store Buffer         Cache(共享)
─────────────────────────────────────────────────────────────
store data = 42    → 进入 Store Buffer →  [等待提交]
store flag = true  → 进入 Store Buffer →  [等待提交]
MFENCE             → 强制刷新 Store Buffer  → data=42, flag=true 提交到 Cache

CPU Core 1:                                         Cache(共享)
─────────────────────────────────────────────────────────────
load flag          ← 从 Cache 读(必须看到 flag=true 后才能继续)
MFENCE / acquire   ← 确保后续 load 看到最新 Cache 状态
load data          ← 从 Cache 读 → 一定是 42

关键点

  • 内存屏障强制刷新 Store Buffer,让 store 提交到 Cache(对其他核心可见)
  • 缓存一致性协议(MESI)负责在 Cache 之间传播这个更新
  • acquire load 确保从 Cache 读时看到最新状态(不使用过期的缓存行)

所以:

防重排(编译器/CPU内部)+ 强制可见性(跨线程)
         ↑                       ↑
         同一个机制,两种效果,不可分割

四、Store-Load 重排

1. 原因

根本原因:Store Buffer(写缓冲区)

当 CPU 执行写操作(Store)时,如果直接写入L1 Cache,由于多核之间的“缓存一致性协议(如MESI)”,CPU 必须等待其他核心确认并作废它们对应的缓存行,这个等待过程比较漫长(CPU 视角)。为了不阻塞 CPU,核心会先把数据写到 Store Buffer 中,然后继续执行后续指令。

现代 CPU 架构:

CPU Core
  ↓ store
[Store Buffer]  ← store 先写这里(速度快,不用等Cache响应)
  ↓ 异步刷新
[L1 Cache]
  ↓
[L2 Cache]
  ↓
[LLC / 内存]
问题场景(Dekker 互斥算法的经典失败案例):

初始值:X = 0, Y = 0

Thread 1:              Thread 2:
  store X = 1            store Y = 1
  load R1 = Y            load R2 = X

期望:R1=1 或 R2=1 至少有一个成立
实际:R1=0 且 R2=0 竟然可能发生!(x86上也会!)

时序解析

Time →
Thread 1: store X=1 → [Store Buffer]    ← 还没提交到 Cache!
Thread 1: load  Y=0 ← 从 Cache 读(Y 的 store 还在 Thread2 的 Store Buffer 里)
Thread 2: store Y=1 → [Store Buffer]    ← 还没提交到 Cache!
Thread 2: load  X=0 ← 从 Cache 读(X 的 store 还在 Thread1 的 Store Buffer 里)
// 结果:R1=0, R2=0。两个 store 都"消失了"

Store-Load 重排的本质:不是 CPU 真的"调换了顺序",而是 store 在 store buffer 里异步等待,而 load 已经直接去 cache 读了。效果上等价于 load 跑到了 store 之前。

Store-Load 重排的理解:从外部观察者(其它 CPU core)的视角看,load 跑到 store 的前头了。因为从外部观察者的立场来看,store完成的标志,是“你得让我看见”。现在,在没有让我看到你写的值的情况下,你先执行了后面的 load 指令,那对我来说,你就是先读后写了。

注意:x86/TSO 只允许 Store→Load 重排,其他三种(Load-Load, Load-Store, Store-Store)x86 不允许。ARM 四种都允许。

2. 解决方案

方法1:在 store 和 load 之间插入 Full Barrier

// x86
asm volatile("mfence" ::: "memory");

// C++ 标准方式
std::atomic_thread_fence(std::memory_order_seq_cst);
Thread 1:
  store X = 1
  MFENCE          ← 强制刷新 Store Buffer,X=1 提交到 Cache
  load R1 = Y     ← 此时 Y 的最新值一定可见

Thread 2:
  store Y = 1
  MFENCE
  load R2 = X     ← 此时 X=1 一定可见

方法2:使用 seq_cst 原子操作

std::atomic<int> X{0}, Y{0};

// Thread 1
X.store(1, std::memory_order_seq_cst);   // 含隐式 Full Barrier
int r1 = Y.load(std::memory_order_seq_cst);

// Thread 2  
Y.store(1, std::memory_order_seq_cst);
int r2 = X.load(std::memory_order_seq_cst);

// 保证:r1=1 或 r2=1 至少一个成立