文章
STM:为什么 Rust 和 Go 没有把它作为核心并发机制?
从 STM 的事务模型出发,对比 Haskell、Rust 与 Go 的并发设计哲学,重点解释 Rust 所有权、Borrowing、Send/Sync 与共享状态边界,并分析 STM 的优势、限制与系统设计价值。
目录
- 一、STM 到底解决什么问题?
- 使用 Mutex
- 二、STM 的核心不是“无锁”,而是可组合性
- Composability —— 可组合性
- 三、Haskell 为什么特别适合 STM?
- 因此 Haskell STM 不只是简单提供一个 transaction {} 语法,而是建立了一整套完整的并发状态抽象。
- 四、Rust 有没有 STM?
- 五、Go 有没有 STM?
- 六、为什么 Rust 没有把 STM 放进语言核心?
- 七、Rust 所有权到底解决了什么?
- 用所有权减少共享。
- 八、Borrowing 进一步定义了“访问边界”
- 九、但 Ownership 并没有消灭共享状态
- 这一层。
- 十、Arc 只解决“共享所有权”,不解决“共享可变访问”
- 十一、Send 和 Sync 是 Rust 共享状态边界的重要组成部分
- 很多情况下,这些问题在编译期就能够得到答案。
- 十二、Rust 的“共享状态边界”在哪里?
- 第一层:Ownership Boundary
- 数据直接转移。
- 第二层:Borrowing Boundary
- 第三层:Thread-Safety Boundary
- 这就是 Rust 与 STM 非常重要的分界。
- 十三、因此 Rust 的并发模型其实是逐层升级的
- 这时候才真正开始触碰 STM 所解决的问题。
- 十四、Rust Channel 为什么也很重要?
- 十五、STM 与 Rust 的真正关系
- 对复杂共享状态提供更强的可组合性。
- 整体事务化。 这正是 Rust 当前标准同步原语没有直接提供的抽象。
- 十六、STM 的另一个关键问题:事务代码可能重复执行
- 最终可能出现重复消息。
- 十七、这也是 STM 与副作用之间最重要的边界
- 思想可以联系起来理解。
- 十八、为什么 Rust 更倾向 Mutex / Atomic / Lock-free?
- 仍然非常有价值。
- 十九、为什么 Go 更没有动力采用 STM?
- 二十、三种语言其实是三条不同的并发路线
- 二十一、STM 最适合什么场景?
- 二十二、STM 不适合什么?
- 二十三、STM 与数据库事务为什么很像?
- 它们不完全等价,但共享很多重要的并发控制思想。
- 二十四、STM 与 Lock-free 不是一回事
- 二十五、为什么 STM 依然值得学习?
- 二十六、从系统设计角度,应该如何理解 STM?
- 二十七、一张图看懂三种模型
- 二十八、最终结论
- 二十九、为什么 STM 依然值得学习?
- 延伸阅读
- 一句话总结
STM(Software Transactional Memory,软件事务内存)是一种不同于 Mutex、Channel 和 Atomic 的并发编程模型。 它的核心思想很简单:
把一组对共享内存的读写操作组织成一个事务,并由运行时负责冲突检测、提交和重试。 因此,STM 真正解决的问题并不是“如何替代锁”,而是: 如何以一种可组合的方式,对复杂共享状态进行原子更新。 这也是理解 Haskell、Rust 和 Go 三种并发模型差异的一个很好切入点。
一、STM 到底解决什么问题?#
假设内存中存在两个账户:
Alice = 100
Bob = 50
现在需要执行:
Alice -= 30
Bob += 30
使用 Mutex#
传统做法需要显式管理锁:
lock Alice
lock Bob
Alice -= 30
Bob += 30
unlock Bob
unlock Alice
一旦系统中存在多个共享对象,就必须开始考虑:
锁的获取顺序
锁的持有范围
锁竞争
死锁
中间状态
而 STM 的思路不同:
Transaction
↓
读取共享状态
↓
执行计算
↓
记录读集 / 写集
↓
提交前验证
↓
是否冲突?
┌────┴────┐
否 是
│ │
Commit Retry
事务只有在验证成功后才真正修改共享状态。 因此,程序员表达的是:
“这几个状态需要一起修改”
而不是:
“我要先锁 A,再锁 B,最后按什么顺序释放”
二、STM 的核心不是“无锁”,而是可组合性#
这是理解 STM 最重要的一点。 很多人第一次接触 STM 时,会直接把它理解成:
STM = 不使用 Mutex 的并发编程。 实际上,这并不是 STM 最有价值的地方。 STM 真正重要的能力是:
Composability —— 可组合性#
假设系统中有:
withdraw()
deposit()
transfer()
使用锁时,每个函数可能都有自己的锁策略。 当这些操作组合起来时,需要重新考虑:
谁持有什么锁?
锁的获取顺序是什么?
是否可能死锁?
中间状态是否会被其他线程观察到?
STM 则可以把多个事务操作继续组合成一个更大的事务。 抽象地看:
withdraw()
+
deposit()
↓
larger transaction
最终仍然具有事务的原子性。 所以 STM 更准确的定位是:
一种用于组合复杂共享状态操作的并发控制模型。
三、Haskell 为什么特别适合 STM?#
Haskell 是 STM 最经典的语言之一。 GHC 提供了完整的 STM 抽象,包括:
TVar
atomically
retry
orElse
典型代码:
atomically $ do
a <- readTVar alice
b <- readTVar bob
writeTVar alice (a - 30)
writeTVar bob (b + 30)
这里最值得关注的是 retry。
例如余额不足:
withdraw amount = do
balance <- readTVar account
if balance >= amount
then writeTVar account (balance - amount)
else retry
retry 并不是:
while balance < amount:
sleep()
它表达的是:
当前事务依赖的条件还没有满足,因此暂停当前事务;当相关事务状态发生变化后,再重新尝试。 这就使得“等待条件成立”本身也成为事务模型的一部分。 而
orElse则可以表达:
先尝试事务 A
如果 A 无法继续
↓
尝试事务 B
因此 Haskell STM 不只是简单提供一个 transaction {} 语法,而是建立了一整套完整的并发状态抽象。#
四、Rust 有没有 STM?#
有。
Rust 生态存在第三方 STM 实现,例如 stm crate,提供了与 Haskell STM 类似的事务抽象。
概念上可以表达:
atomically(|tx| {
let a = alice.read(tx)?;
let b = bob.read(tx)?;
alice.write(tx, a - 30)?;
bob.write(tx, b + 30)?;
Ok(())
});
具体实现通常需要维护事务元数据,例如:
Read Set
Write Set
Version
Validation Information
事务执行过程中并不一定立即修改真实共享状态,而是先记录操作。 提交时:
验证读集
↓
检测冲突
↓
成功 → Commit
失败 → Retry
所以:
Rust 并不是做不到 STM,而是没有选择把 STM 作为主流并发模型。
五、Go 有没有 STM?#
同样有。 Go 社区也存在第三方 STM 实现,可以提供类似:
Atomic Transaction
Transactional Variable
Retry
这样的机制。 但 STM 同样不是 Go runtime 或标准库中的核心并发抽象。 这是因为 Go 选择的主路径并不是“让共享内存变得事务化”,而是:
尽量通过消息传递降低共享状态的复杂度。
六、为什么 Rust 没有把 STM 放进语言核心?#
这并不是因为 Rust 无法实现 STM。 更重要的原因是:
Rust 已经选择了另一套解决共享状态问题的核心机制。
七、Rust 所有权到底解决了什么?#
理解 Rust 与 STM 的关系,首先必须把一个非常容易混淆的问题拆开:
“数据属于谁?”与“数据是否被多个线程共享?”其实是两个不同的问题。 Rust 的 Ownership 首先解决的是资源归属权。 例如:
let task = Task::new();
worker(task);
这里 task 被 move 到 worker。
可以把它理解成:
Thread A
│
│ ownership transfer
▼
Thread B
转移完成以后,原来的线程不再拥有这份数据。 于是很多问题根本不需要:
lock
atomic
transaction
因为系统直接规定:
同一时刻只有一个地方拥有这份可变状态。 这就是 Rust 最重要的并发设计之一:
用所有权减少共享。#
八、Borrowing 进一步定义了“访问边界”#
Ownership 解决的是“谁拥有”。 Borrowing 解决的是:
在不转移所有权的情况下,谁可以访问。 Rust 的两个核心形式:
&T
&mut T
可以粗略理解为:
&T
→ 只读共享
&mut T
→ 独占可变访问
例如:
fn read_config(config: &Config) {
// 多个调用者可以同时读取
}
而:
fn update_config(config: &mut Config) {
// 当前调用者独占可变访问
}
这背后的关键规则可以概括成:
多个 &T
或
一个 &mut T
而不能同时存在:
&T + &mut T
这种限制非常重要,因为它把大量“共享内存并发错误”提前变成了编译错误。 所以 Rust 的思路不是:
共享以后再想办法保证安全
而是:
先限制谁能访问
↓
再考虑如何共享
九、但 Ownership 并没有消灭共享状态#
这是特别容易被误解的一点。 Rust 并不是:
“有 Ownership,所以不存在共享内存。” 实际上完全不是。 Rust 可以非常自然地构造:
Arc<T>
Arc<Mutex<T>>
Arc<RwLock<T>>
Arc<AtomicU64>
例如:
use std::sync::{Arc, Mutex};
let state = Arc::new(Mutex::new(0));
这意味着:
Arc
┌───┴───┐
│ │
Thread A Thread B
│ │
└───┬───┘
▼
Mutex<T>
│
shared state
所以:
Ownership 并不是消灭共享状态,而是首先尝试把“唯一拥有”作为默认模型。 只有当需求本身确实要求多个执行单元共享数据时,才重新进入:
Arc
↓
Mutex / RwLock / Atomic
这一层。#
十、Arc 只解决“共享所有权”,不解决“共享可变访问”#
这一点尤其值得区分。 很多人看到:
Arc<T>
就认为“线程安全了”。
实际上 Arc 只解决:
多个线程如何共同持有同一份数据。 它并不自动解决: 多个线程如何安全地修改这份数据。 所以可以把 Rust 的共享状态层次看成:
Ownership
↓
Shared Ownership
↓
Arc
↓
Shared Mutable State
↓
Mutex / RwLock / Atomic
而 STM 则位于更高一层:
多个 Shared Mutable State
↓
复杂状态关系
↓
Transactional Composition
↓
STM
这就是为什么 STM 与 Rust 的关系不是:
STM vs Ownership
而更接近:
Ownership
↓
是否需要共享?
↓
Shared State
↓
Mutex / Atomic / RwLock
↓
是否需要复杂事务组合?
↓
STM
十一、Send 和 Sync 是 Rust 共享状态边界的重要组成部分#
仅仅有 Ownership 和 Borrowing 还不够。 Rust 还需要回答:
这份数据能不能跨线程发送? 以及: 这份数据能不能被多个线程通过共享引用访问? 这就是:
Send
Sync
可以用一个非常粗略但实用的方式理解:
Send
→ T 的所有权可以安全地发送到另一个线程
Sync
→ &T 可以安全地在线程之间共享
于是 Rust 的并发安全模型实际上变成:
Ownership
↓
Borrowing
↓
Send / Sync
↓
允许跨线程共享的边界
↓
Arc / Mutex / Atomic / Channel
这意味着 Rust 的共享状态并不是一个模糊的“大家随便访问”。 而是存在非常清晰的类型边界:
这个类型能不能跨线程?
这个引用能不能在线程间共享?
这个状态是否拥有内部可变性?
这个修改是否需要同步?
很多情况下,这些问题在编译期就能够得到答案。#
十二、Rust 的“共享状态边界”在哪里?#
可以把 Rust 并发设计理解成三道边界。
第一层:Ownership Boundary#
谁拥有数据?
默认倾向:
One Owner
例如:
let data = Vec::new();
worker(data);
数据直接转移。#
第二层:Borrowing Boundary#
谁可以暂时访问数据?
主要是:
&T
&mut T
这一步主要控制:
共享读
独占写
生命周期
第三层:Thread-Safety Boundary#
这个数据能不能跨线程?
主要体现为:
Send
Sync
一旦真的需要共享可变状态:
Arc<Mutex<T>>
Arc<RwLock<T>>
Arc<Atomic<T>>
就说明程序已经主动越过了:
“单一所有者 + 借用” 这条简单边界。 进入共享状态以后,Rust 并没有继续假装这个问题不存在,而是要求你显式选择:
Mutex?
RwLock?
Atomic?
Channel?
Actor?
这就是 Rust 与 STM 非常重要的分界。#
十三、因此 Rust 的并发模型其实是逐层升级的#
可以把整个过程画成:
是否需要共享?
│
┌──────┴──────┐
│ │
否 是
│ │
▼ ▼
Ownership Arc
│ │
│ ┌─────┴─────┐
│ │ │
│ Read Write
│ │ │
│ RwLock Mutex
│ │
└─────────┬───────────┘
│
状态关系复杂?
│
┌─────┴─────┐
│ │
否 是
│ │
Lock/Atomic STM
这表达了一个重要的工程原则:
不要一上来就把所有东西都设计成
Arc<Mutex<T>>。Arc<Mutex<T>>很方便,但它意味着你已经进入了:
Shared Ownership
+
Shared Mutable State
+
Runtime Synchronization
这个世界。 而 Rust 更希望你优先考虑:
Can I move it?
Can I borrow it?
Can I send it?
Can I avoid sharing it?
只有这些答案都是否定的,才需要:
Arc
Mutex
RwLock
Atomic
而当共享状态进一步演化成:
多个对象
多个条件
需要组合
需要原子提交
这时候才真正开始触碰 STM 所解决的问题。#
十四、Rust Channel 为什么也很重要?#
Rust 还有另一条非常重要的路线:
Ownership Transfer
+
Message Passing
例如:
tx.send(data)?;
这里不是让两个线程共同修改:
Arc<Mutex<Data>>
而是:
Thread A
│
│ ownership
▼
Channel
│
▼
Thread B
数据的所有权发生转移。 这和 Go 的 Channel 思想有相似之处,但 Rust 额外把 Ownership 纳入类型系统。 因此可以把 Rust 并发设计看成两个方向:
方向 A:减少共享
Ownership
Channel
Message Passing
方向 B:管理共享
Arc
Mutex
RwLock
Atomic
而 STM 又是:
方向 C:事务化共享状态
Transactional Memory
十五、STM 与 Rust 的真正关系#
因此,不应该问:
“为什么 Rust 没有 STM?” 更准确的问题是: Rust 已经有 Ownership、Borrowing、Send/Sync、Mutex、Atomic、Channel 等一整套共享状态控制机制,那么 STM 还能增加什么? 答案主要是:
对复杂共享状态提供更强的可组合性。#
例如存在:
Account
Inventory
Quota
Order
并且要求:
余额足够
AND
库存存在
AND
额度足够
同时修改:
balance -= 50
inventory -= 1
quota -= 50
这时:
Arc<Mutex<Account>>
Arc<Mutex<Inventory>>
Arc<Mutex<Quota>>
开始出现复杂的锁组合问题。 STM 提供的是:
read
check
write
commit
整体事务化。 这正是 Rust 当前标准同步原语没有直接提供的抽象。#
十六、STM 的另一个关键问题:事务代码可能重复执行#
STM 有一个非常重要的特征:
Execute
↓
Conflict
↓
Abort
↓
Retry
因此,一个事务内部的代码可能执行多次。 例如:
atomically(|tx| {
println!("hello");
// ...
});
如果发生冲突,理论上可能得到:
hello
hello
hello
这说明一个重要问题:
事务内部的代码必须允许重复执行。 因此下面这些操作就非常危险:
HTTP request
Kafka publish
MQTT publish
文件写入
日志输出
调用外部服务
因为这些操作通常不能随着事务回滚。 例如:
Transaction
↓
Kafka.publish()
↓
Conflict
↓
Retry
↓
Kafka.publish() again
最终可能出现重复消息。#
十七、这也是 STM 与副作用之间最重要的边界#
可以把代码分成两类:
Transactional State
│
├── 可回滚
├── 可重复执行
└── 只修改事务状态
External Effects
│
├── 网络
├── 文件
├── Kafka / MQTT
├── 数据库外部调用
└── 其他 IO
更合理的系统结构是:
STM Transaction
│
┌────────┴────────┐
│ │
State Update Decision
│ │
└────────┬────────┘
│
Commit
│
▼
External Effects
也就是:
先提交内部状态,再执行不可回滚的外部副作用。 这和很多现代系统中的:
Transactional Outbox
State Machine
Event Publishing
思想可以联系起来理解。#
十八、为什么 Rust 更倾向 Mutex / Atomic / Lock-free?#
另一个原因是性能模型。 STM 通常是一种乐观并发控制机制:
执行
↓
记录读写集
↓
验证
↓
Commit
如果并发冲突很低:
STM
↓
很自然
但是如果冲突频繁:
执行 100 μs
↓
冲突
↓
重新执行 100 μs
↓
再次冲突
↓
再次执行
一个事务可能计算多次,但最终只成功提交一次。 这可能带来:
更多 CPU
更多事务管理开销
更多 cache traffic
更复杂的延迟行为
而 Rust 经常用于:
系统软件
网络服务
数据库
消息系统
Runtime
基础设施
高性能计算
这些场景通常非常关注:
Latency
Throughput
Memory Usage
Cache Behavior
Contention
Allocation
因此显式的:
Mutex
RwLock
Atomic
Sharded Lock
Lock-free Structure
仍然非常有价值。#
十九、为什么 Go 更没有动力采用 STM?#
Go 的方向更加明显:
Goroutine
↓
Channel
↓
Message Passing
Go 经典的并发思想可以概括为:
不要通过共享内存通信,而是通过通信共享内存。 这并不意味着 Go 不支持共享内存。 Go 仍然提供:
sync.Mutex
sync.RWMutex
sync.Once
sync/atomic
只是 Go 更倾向于让系统结构变成:
Goroutine A
│
│ Message
▼
Channel
│
▼
Goroutine B
而不是:
Goroutine A ─┐
├── Shared State
Goroutine B ─┘
所以 Go 与 STM 在设计哲学上存在明显差异:
STM
↓
让共享状态更容易管理
Go
↓
尽量减少直接共享状态
二十、三种语言其实是三条不同的并发路线#
可以用下面的方式理解:
Haskell
↓
Shared State
↓
STM
↓
Transactional + Composable Concurrency
Go
↓
Reduce Shared State
↓
Goroutine + Channel + CSP
↓
Message Passing
Rust
↓
Ownership
↓
Reduce Shared Mutable State
↓
Send / Sync
↓
Mutex / Atomic / Channel
因此并不是:
Haskell STM
↓
Rust / Go 缺失
而是:
Haskell → 选择 STM 作为核心并发抽象
Go → 选择 CSP / Channel
Rust → 选择 Ownership + Type System
二十一、STM 最适合什么场景?#
STM 特别适合这样的状态管理问题:
多个共享变量
+
复杂一致性条件
+
需要原子更新
例如:
库存
余额
额度
资源池
任务队列
状态机
多个关联计数器
比如一次操作要求:
库存 > 0
AND
余额 >= 50
AND
额度 >= 50
然后:
库存 -= 1
余额 -= 50
额度 -= 50
这实际上非常适合 STM:
Read
↓
Check
↓
Update
↓
Atomic Commit
因为这里真正的问题不是“某个变量怎么加锁”,而是:
多个状态之间的关系必须作为一个整体保持一致。
二十二、STM 不适合什么?#
STM 不适合把整个业务逻辑都包进去。 尤其不适合直接执行:
HTTP Request
RPC Call
Kafka Publish
MQTT Publish
文件写入
外部数据库调用
第三方 API
因为 STM 的事务可能:
Commit
或者
Rollback + Retry
而网络请求无法简单 rollback。 因此,如果系统存在大量外部副作用,通常还需要:
Transaction
↓
Commit State
↓
Event / Outbox
↓
External Side Effect
这时问题就从 STM 延伸到了:
Transaction
↓
Event
↓
Message Broker
↓
Distributed System
二十三、STM 与数据库事务为什么很像?#
这是 STM 最值得深入研究的地方之一。 STM 和数据库事务并不是同一个东西,但两者存在非常强的思想对应。 可以看到一条很有意思的技术链:
STM
↓
Read Set / Write Set
↓
Conflict Detection
↓
Optimistic Concurrency Control
↓
MVCC
↓
Database Transaction
典型流程都类似:
读取状态
↓
执行计算
↓
产生临时修改
↓
检测冲突
↓
成功提交 / 放弃
进一步向底层研究,还可以继续连接:
STM
↓
OCC
↓
MVCC
↓
Lock-free
↓
Hardware Transactional Memory
它们不完全等价,但共享很多重要的并发控制思想。#
二十四、STM 与 Lock-free 不是一回事#
这两个概念也经常被混淆。 STM 关注的是:
如何组织事务以及处理事务冲突。 Lock-free 关注的是: 如何通过原子操作设计数据结构,使系统整体不会因为某个线程阻塞而停止进展。 所以:
STM
≠
Lock-free
STM 甚至完全可以使用锁或原子操作作为内部实现机制。 真正需要比较的是:
编程模型
性能模型
冲突模型
可组合性
实现复杂度
进度保证
而不是简单比较:
STM vs Mutex
二十五、为什么 STM 依然值得学习?#
如果只是为了日常写 Rust 或 Go:
Mutex
Atomic
Channel
Send / Sync
Goroutine
显然更加常用。 但是从系统设计和并发控制理论的角度,STM 非常值得学习。 它可以帮助建立以下几个核心概念:
Atomicity
Isolation
Conflict Detection
Optimistic Concurrency
Retry
Commit
Composability
理解这些概念以后,再去学习:
数据库事务
MVCC
消息系统
Actor
Scheduler
Runtime
Lock-free Algorithm
分布式事务
会容易很多。 从系统设计角度看,STM 最值得学习的不是某一个 Rust 或 Go 库,而是它背后的思想:
把复杂并发状态操作提升到“可组合事务”的抽象层。
二十六、从系统设计角度,应该如何理解 STM?#
不要把 STM 理解成:
“一个比 Mutex 更先进的锁。” 更准确的理解是: STM 是另一套并发编程模型。 它回答的是: 当系统中存在多个共享状态,并且这些状态之间存在复杂的一致性关系时,能不能把它们的更新过程抽象成一个可组合的事务? Haskell 的答案是:
TVar
+
atomically
+
retry
+
orElse
Go 更倾向于:
Goroutine
+
Channel
+
Message Passing
Rust 更倾向于:
Ownership
+
Borrowing
+
Send / Sync
+
Mutex / Atomic / Channel
因此,这三种语言不是简单地“缺少”或“拥有”某个并发 API。 它们实际上是在回答同一个问题:
如何管理并发系统中的状态? 只是选择了不同的抽象层次。
二十七、一张图看懂三种模型#
并发共享状态
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Haskell Go Rust
│ │ │
STM CSP Ownership
│ │ │
▼ ▼ ▼
Transaction Message Type System
│ Passing │
▼ ▼ ▼
TVar Channel Send / Sync
│ │ │
▼ ▼ ▼
retry / orElse Select Mutex / Atomic
二十八、最终结论#
Rust 和 Go 不是没有 STM。 两者都可以通过第三方库实现 STM。 真正的区别在于:
STM 没有成为 Rust 和 Go 的核心并发抽象。 Rust 的关键设计路径可以概括为:
Ownership
↓
Borrowing
↓
Send / Sync
↓
是否必须共享?
↓
Arc / Mutex / RwLock / Atomic / Channel
↓
是否存在复杂的跨状态原子更新?
↓
STM
所以 Rust 的独特之处并不是“没有共享状态”,而是:
它把共享状态变成一个需要显式跨越的边界。 从设计角度看,Rust 会不断要求你回答:
谁拥有数据?
谁可以读取?
谁可以修改?
是否真的需要共享?
能否通过 ownership transfer?
能否通过 channel 传递?
如果必须共享,应该用 Mutex、RwLock 还是 Atomic?
如果多个共享状态必须组成一个原子操作,是否需要事务模型?
这与 Haskell STM 的起点不同。 Haskell 更自然地表达:
共享状态
↓
TVar
↓
STM
↓
atomically
Rust 更自然地表达:
数据
↓
Ownership
↓
Borrowing
↓
Send / Sync
↓
Shared State
↓
Synchronization Primitive
Go 则更自然地表达:
Goroutine
↓
Channel
↓
Message Passing
因此:
Haskell 更关注“如何把共享状态事务化并进行组合”;Go 更关注“如何通过消息传递减少直接共享”;Rust 更关注“如何通过所有权和类型系统明确共享状态的边界”。
二十九、为什么 STM 依然值得学习?#
如果只是为了日常写 Rust 或 Go:
Mutex
Atomic
Channel
Send / Sync
Goroutine
显然更加常用。 但是从系统设计和并发控制理论的角度,STM 非常值得学习。 它可以帮助建立以下几个核心概念:
Atomicity
Isolation
Conflict Detection
Optimistic Concurrency
Retry
Commit
Composability
理解这些概念以后,再去学习:
数据库事务
MVCC
消息系统
Actor
Scheduler
Runtime
Lock-free Algorithm
分布式事务
会容易很多。 从系统设计角度看,STM 最值得学习的不是某一个 Rust 或 Go 库,而是它背后的思想:
把复杂并发状态操作提升到“可组合事务”的抽象层。
延伸阅读#
- GHC STM:官方文档
- Rust
stmcrate:docs.rs - Rust The Book:Concurrency / Send / Sync:官方文档
- Go:Share Memory By Communicating:官方文档
一句话总结#
Haskell 通过 STM 把共享状态组织成可组合事务;Go 更倾向于通过 CSP 和 Channel 减少直接共享;Rust 则通过 Ownership、Borrowing、Send/Sync 与显式同步原语管理共享状态。STM 不是 Rust 或 Go 缺少的能力,而是它们没有选择把 STM 作为核心并发模型。