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

推荐订阅源

H
Help Net Security
Scott Helme
Scott Helme
爱范儿
爱范儿
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky
V
V2EX
腾讯CDC
博客园_首页
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
Microsoft Azure Blog
Microsoft Azure Blog
B
Blog
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
罗磊的独立博客
Recorded Future
Recorded Future
博客园 - 聂微东
O
OpenAI News
S
Secure Thoughts
Hacker News: Ask HN
Hacker News: Ask HN
S
Schneier on Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
宝玉的分享
宝玉的分享
K
Kaspersky official blog
N
Netflix TechBlog - Medium
T
The Exploit Database - CXSecurity.com
Google Online Security Blog
Google Online Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Webroot Blog
Webroot Blog
云风的 BLOG
云风的 BLOG
Simon Willison's Weblog
Simon Willison's Weblog
C
Check Point Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 热门话题
美团技术团队
L
Lohrmann on Cybersecurity

博客园 - egmkang

配眼镜最佳实践 [Python]解密pyc文件 C# Protobuf如何做到0分配内存的序列化/反序列化(2) Flash----一种VirtualActor模式的分布式有状态系统原型 [投资]对价值投资新的理解 通过Consul Raft库打造自己的分布式系统 Dapr实现分布式有状态服务的细节 [06] 优化C#服务器的思路和工具的使用 [05] 通过P/Invoke加速C#程序 [04] C# Alloc Free编程之实践 [03] C# Alloc Free编程 [02] 多线程逻辑编程 [01] C#网络编程的最佳实践 C#如何正确的做深拷贝 C# Protobuf如何做到0分配内存的序列化 最近很火的namebase羊毛, 手把手教你怎么薅 DotNetty发送请求的最佳实践 C# protobuf自动更新cs文件 多读少写场景下多线程锁冲突的降低
CPU实现原子操作的原理
egmkang · 2020-12-04 · via 博客园 - egmkang

586之前的CPU, 会通过LOCK锁总线的形式来实现原子操作. 686开始则提供了存储一致性(Cache coherence),  这是多处理的基础, 也是原子操作的基础.

1. 存储的粒度

存储的组织形式(粒度)是以CacheLine为单位的, 通常为64字节甚至更高(早期也有32字节的). 然后几组CacheLine组成一个小的LRU(或者其他替换规则).

2. 协议

存储一致性(CC)一般是通过MESI协议, 以及后续的变种协议, 例如Intel的MESIF协议和AMD的MOESI协议, 来实现的. 

以MESI协议为例:

Modified: 独占CacheLine, 已经修改了, 但是还未同步到主存

Exclusive: 独占, 并且和主存一致

Shared : 共享的, 其他core也拥有该CacheLine, 并且与主存中一致

Invalid: 表示该CacheLine不可用

3. 通讯

有了协议, 那么就需要通讯来实现协议(存储的状态). 通讯有两种, 一种是广播/侦听, 一种是目录式.

广播/侦听顾名思义就是存储状态的变更, 会被广播到其他core上面去, 进而去维护CacheLine的状态. 很明显这种方式会浪费大量的流量, 而且难以扩展, CPU核数多了, 总线就是明显的瓶颈.

目录式, 就是把改变通知给具体的core, 从而避免广播. 但是考虑一种极端情况, 如果很多个core都在访问同一个CacheLine, 那还是不能避免(事实上的)广播. 所以, 多线程编程时, 共享同一个CacheLine不是一个好的选择.

 有了上面的东西, 现在我们来考虑原子操作的实现:

1. 原子的Load/Store

由于CPU对缓存的管理是以CacheLine为单位的, 所以在一个CacheLine内load/store实际上都是原子的. Load和Store一个8字节对象, 不可能高4位和低4位是分开操作的(从而搞成俩值). 

但是光有这个实际上还不够, CPU对CacheLine的修改不是立即写到主存里面去, 所以其他Core看到的值就有可能是老的值, 所以这时候还需要fence来读到最新的值; 至于写, 那一定需要写权限, 即M或者E状态, 而这两个权限里面都有最新的值(只是你刚才读到的不一定是最新的, 所以有可能用老值覆盖了新值).

2. FetchAndAdd

这是比load和store稍微复杂的操作, 实际上是一个复合操作. 但是有了M和E状态, 就很好理解了:

lock(CacheLine)
v := load(obj)
v += add
store(obj, v)
release(CacheLine)

x86里面是xadd指令.

3. CompareAndSwap

那么CAS, 也就可以猜出来:

lock(CacheLine)
v := load(obj)
if v != expected {
  store(obj, new_value)
}
release(CacheLine)

x86里面是xchg

这里说的lock和release均表示对该CacheLine独占和解出独占的意思.

关于原子操作的原理, 鲜有资料表表示其具体怎么做的, 很有可能是过于偏向于硬件. 但是对MESI等协议的思考, 实际上还是能猜到CPU内部的实现(至少七八不离十).  好在找到两个资料, 一个是<<并行多核体系结构基础>>和<<从鲲鹏920了解现代服务器实现和引用>>. 其中鲲鹏920内存模型章节这么写到:

原子指令在软件上看来逻辑并不复杂,但在微架构上看,成本是很高的。如果我们把CPU 和内存都看做是总线上的一个个独立的实体,有一个CPU要做CAS指令,这个CPU需要先从 内存中读一个值,同时要在内存控制器上设置一个标志,保证其他CPU写不进去,等它比 较完了,然后再决定写一个值回去,才会让其他CPU写入。

不同微架构实现有不同方法对行为进行优化,在鲲鹏920上,原子指令的请求需要在 L3Cache上进行排队,保证在原子操作的多个动作之间能维持原子指令要求的语义。这个 排队本身也有成本。所以没有原子需要就不要轻易用原子变量,这其实是有成本的。

 并行多核体系结构这么写到:

幸运的是, 缓存一致性协议提供了原子性被保障的基础. 举例来说, 当遇到一个原子指令时, 这个协议知道需要保证原子性. 他首先获得对存储单元M的"独家所有权" (通过将其他包含M的缓存块中的拷贝都置为无效). 当获得独家所有权之后, 这个协议会确保只有一个处理器能够访问这个块, 而如果其他处理器在此时想要访问的话就会经历缓存缺失, 接下来原子指令就可以执行. 在原子指令持续期间, 其他处理器不允许"偷走"这个块. 距离来说, 如通另一个处理器要求读或者写这个块, 这个块就被"偷"了(如块被清理, 块的状态被降级为无效). 在原子指令完成之前暴露块会破坏指令的原子性, ......

参考:

1) 并行多核体系结构基础

2) 从鲲鹏920了解现代服务器实现和应用