Lec 17事务内存(Transactional Memory)
Lec 17 事务内存(Transactional Memory)
MIT 6.5900 Fall 2024 · Daniel Sanchez(基于 Christos Kozyrakis 的幻灯片) 主题:并行编程之难、并发控制、事务内存(TM)语义、数据版本管理与冲突检测、硬件事务内存(HTM)
TL;DR
- 事务内存核心语义:借鉴数据库事务模型,为共享内存并行编程提供原子性(Atomicity)、隔离性(Isolation)与可串行化(Serializability)的声明式同步抽象。
- 版本管理与冲突检测:数据版本支持急切更新(Undo Log)与惰性缓冲(Write Buffer);冲突检测涵盖访问时即检的悲观策略与提交时校验的乐观策略。
- 硬件事务内存(HTM):巧妙复用私有缓存行中的 R/W 标记位跟踪读写集,并借力既有一致性协议总线/目录探测读写冲突,实现近乎零开销的推测提交与回滚。
一、动机:并行编程很难
多核的理由是性能/成本曲线:与其一个高性能高成本的核,不如用多个适中而高效的核。但并行编程很难:
- 把算法划分为任务;
- 把任务映射到线程;
- 加同步(锁、屏障……)以避免数据竞争并保证任务顺序;
- 陷阱:可扩展性、局部性、死锁、活锁、公平性、竞争、可组合性、可移植性……
例:哈希表
顺序实现的 lookup 非线程安全(并发 insert 与 lookup 会竞争),需要同步。
- 粗粒度锁:给所有方法加
lock/unlock。问题:把对互不相关的桶的操作也串行化了; - 细粒度锁:每桶一把锁。问题:加锁开销大,且仍过度串行化(如对同一桶的并发只读也被串行)。
性能:在平衡树与哈希表两种负载下,细粒度锁通常优于粗粒度锁,但都难以随处理器数线性扩展。
二、并发控制(Concurrency Control)
为避免共享数据上的竞争,两类思路:
- 停顿(Stall)/ 互斥:保证临界区内至多一个进程,其余等待;
- 推测(Speculate):
- 猜:假设临界区内不会冲突;
- 查:检测是否发生冲突的数据访问;
- 恢复:若冲突则回滚,否则提交。
三、事务内存(TM)
内存事务(memory transaction,[Lomet'77, Knight'86, Herlihy & Moss'93]):一段原子且隔离的内存访问序列,灵感来自数据库事务。
- 原子性(Atomicity,要么全做要么不做):提交时所有写一次性生效;中止时所有写如同未发生;
- 隔离性(Isolation):提交前其他代码看不到这些写;
- 可串行化(Serializability):事务看起来以某个单一串行顺序提交(但具体顺序不保证)。
用 TM 编程:声明式同步
在传统基于锁的并发编程中,程序员必须承担「命令式同步」的繁重负担:不仅要精确识别哪些共享数据需要保护,还必须手动设计细粒度的互斥锁(如 account.mutex),并在所有可能访问该对象的代码路径上严格保持获取锁的全局全序,否则极易引发灾难性的死锁、优先级反转或护航效应(Lock Convoy)。
与之形成鲜明对照的是,事务内存引入了高度抽象的「声明式同步」模型。程序员只需通过 atomic { ... } 语句声明代码块的语义边界——即明确告诉系统「做什么(What)」,而将「怎么做(How)」的底层同步与仲裁机制完全卸载给运行时或硬件。
在底层实现中,系统默认采用推测并发执行:只要不同线程访问的是不相交的内存区域(例如向不同银行账户存款),所有事务就可以完全并行向前推进;只有在检测到真正的读-写(RAW/WAR)或写-写(WAW)数据冲突时,系统才介入仲裁并回滚其中一方,从而天然达成了粗粒度锁的编程简易度与细粒度锁的极致并发性能。
// 用锁 // 用 TM
void deposit(account, amount) { void deposit(account, amount) {
lock(account.mutex); atomic {
int t = bank.get(account); int t = bank.get(account);
t = t + amount; t = t + amount;
bank.put(account, t); bank.put(account, t);
unlock(account.mutex); }
} }
TM 的优势
- 易用:像粗粒度锁一样易用,程序员声明、系统实现;
- 高性能:至少不逊于细粒度锁,自动获得读-读并发与细粒度并发,性能与正确性之间无需权衡;
- 可组合性(Composability):软件模块可安全可扩展地组合(嵌套事务)。
性能:硬件 TM 系统 TCC [Hammond et al., ISCA'04] 在两种负载上随处理器数扩展明显优于锁。
四、TM 实现基础
用推测在不牺牲并发的前提下提供原子性与隔离性。基本要求:
- 数据版本管理(Data versioning);
- 冲突检测与解决(Conflict detection & resolution)。
实现选项:硬件 TM(HTM)、软件 TM(STM)、混合 TM(硬件加速的 STM 与双模系统)。
为什么要硬件 TM
- 单线程 STM 性能有 2–8× 减速(短期使并行编程失去动力,长期不节能);
- 业界已采用 HTM:Intel(自 Haswell)、IBM(POWER8+、Blue Gene、zSeries)、ARM(v9)。
五、数据版本管理策略
为并发事务管理**未提交(新)与已提交(旧)**两个版本:
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 急切版本(Eager,基于撤销日志 undo-log) | 直接更新内存,把旧值记入 undo log | 提交快 | 中止慢 |
| 惰性版本(Lazy,基于写缓冲 write-buffer) | 数据缓冲到写缓冲,提交时才写回内存 | 中止快 | 提交慢 |
急切版本:写 X←15 时直接改内存并把 X:10 记入 undo log;提交丢弃日志,中止时用日志恢复。 惰性版本:写 X←15 进写缓冲,内存仍是 10;提交时写回内存,中止时丢弃写缓冲。
六、冲突检测(Conflict Detection)
须跟踪事务的读集(read-set,事务内读过的地址)与写集(write-set,事务内写过的地址),处理 R-W 与(常见的)W-W 冲突。
1. 悲观检测(Pessimistic)
在 load/store 当时就检查冲突:
- SW:用锁和/或版本号设软件屏障;
- HW:通过一致性动作检查;
- 用竞争管理器(contention manager)决定停顿还是中止,按优先级策略让常见情况快。
四种情形:成功;提前检测后停顿;检测到后中止重启;多事务相互中止导致无进展(无前进保证)。
2. 乐观检测(Optimistic)
在事务尝试提交时才检测冲突:
- SW:用锁/版本号校验读写集;
- HW:用一致性动作校验写集(为写集的 Cache 行取得独占访问),冲突时优先提交中的事务,其他事务稍后可能中止;
- 也可二者结合:不少 STM 对读乐观、写悲观。
冲突检测的权衡
| 悲观 | 乐观 | |
|---|---|---|
| 优点 | 早检测,少做无用功,部分中止转停顿 | 有前进保证,潜在更少冲突、更短加锁(SW)、批量通信(HW) |
| 缺点 | 无前进保证,某些情况下更多中止;加锁问题(SW)、细粒度通信(HW) | 晚检测,仍有公平性问题 |
七、硬件事务内存(HTM)实现
- 数据版本管理:用私有缓存 —— 把写缓冲或 undo log 暂存在缓存中,用缓存元数据跟踪读集与写集(可在私有/共享/多级缓存上做);
- 冲突检测:用缓存一致性协议 —— 一致性查找检测事务间冲突(支持侦听式与目录式一致性);
- 中止时还须恢复寄存器状态 → 取寄存器检查点(乱序核可借助重命名表快照以极小改动支持)。
HTM 设计:每行的 R/W 位
每个缓存行跟踪读集与写集:
- R 位:事务读过该数据(load 时置位);
- W 位:事务写过该数据(store 时置位);
- R/W 位可在字或行粒度;提交或中止时整组清除(gang-clear)。
一致性请求检查 R/W 位以检测冲突:
- 对 W 字的 Shared 请求 = 读-写冲突;
- 对 R 字的 Exclusive 请求 = 写-读冲突;
- 对 W 字的 Exclusive 请求 = 写-写冲突。
行格式示意:V D E Tag | R W Word1 | ... | R W WordN。
示例:惰性 + 乐观 HTM(基于总线)
CPU 改动:寄存器检查点、TM 状态寄存器(状态、处理器指针等);缓存改动:每行 R/W 位。
执行 Xbegin / Load A / Store B←5 / Load C / Xcommit:
- 事务开始:初始化 CPU 与缓存状态,取寄存器检查点;
- Load A:必要时处理缺失,置该行 R 位;
- Store B←5:必要时处理缺失(即使其他核有该行也先取 shared),置该行 W 位;
- 快速两阶段提交:① 校验(对写集行请求独占访问)② 提交(整组清 R/W 位,把写集数据转为有效脏数据)。
冲突检测与中止:对读集/写集查找独占请求 → 命中则中止(作废写集、整组清 R/W 位、恢复检查点)。
HTM 的优势与挑战
优势:
- 常见情况快:零开销跟踪读写集、零开销版本管理、无数据移动的快速提交/中止、对读集持续校验;
- 强隔离:对非事务 load/store 也检测冲突;
- 简化多核一致性与一致性模型(如可用 HTM 实现难做的顺序一致性 SC)。
挑战:
- 性能病态:频繁竞争如何处理?HTM 是否应保证公平/优先级?
- 容量限制:读集+写集超过缓存容量怎么办?
- 虚拟化、I/O、系统调用……;
- 混合 TM 可能两全:硬件处理常见情况但无保证(缓存溢出、中断、syscall 时中止),中止后回退到 STM(Intel RTM 的当前思路),但 HTM 与 STM 如何良好整合仍不清楚;
- 目前程序员采用缓慢/受限,仍须支持非 HTM 系统。
事务内存语义与硬件实现架构总结
- 锁在易用性与并发度之间总有取舍;事务内存用声明式
atomic把"做什么/怎么做"分离,提供原子性、隔离性、可串行化与可组合性; - 实现核心是数据版本管理(急切 undo-log / 惰性 write-buffer)与冲突检测(悲观早检 / 乐观晚检);
- HTM 复用私有缓存做版本管理、复用一致性协议做冲突检测,以每行 R/W 位跟踪读写集,常见情况近零开销;难点在容量、虚拟化与公平性,混合 TM 是折中方向。
硬件事务内存(HTM)的「完美假象」与现实工程困境
硬件事务内存(HTM,如 Intel TSX)曾在学术界和工业界被寄予厚望,被视为终结多核并发编程噩梦的「银弹」。其架构设计堪称微架构复用艺术的典范:将私有 L1/L2 缓存的 Tag 阵列扩展 R/W 位作为瞬态读写集过滤器,同时借用既有的 MESI 一致性协议侦听消息作为天然的写冲突探测器,几乎以零额外硬件代价实现了硬件级 ACID 隔离。
然而,HTM 在真实生产环境中的惨淡收场揭示了微架构抽象的残酷现实:
- 微架构与软件语义的严重阻抗失配:缓存不仅用于保存事务数据,还是通用访存的容量承载介质。一旦事务工作集超过 L1 数据缓存容量(通常仅 32–48 KB),或者遭遇缓存行伪共享、相联度冲突、上下文切换、甚至一条简单的系统调用或 I/O 指令,硬件都不得不被迫触发回滚中止(Abort)。
- 缺乏前进保证(No Forward Progress):HTM 必须依赖软件退化路径(Fallback Lock),导致「事务争相尝试 - 冲突回滚 - 最终抢占全局回退锁」的恶性雪崩效应。
- 微架构侧信道漏洞(如 Speculative Abort / TSX 异步中止侧信道) 更让厂商在安全补丁中直接通过微码禁用了多数老处理器的 TSX 功能。
因此,现代架构演进深刻证明:试图在通用硬件缓存体系之上透明包裹一套全局事务语义,往往会因微架构隐性瓶颈的暴露而付出难以承受的工程代价。
下一讲:VLIW