6-16.【架构设计】在并发场景下,State的一致性如何保证?你更倾向于:锁、Actor、不可变数据,还是 reducer?

0 阅读3分钟

一、问题本质

在并发场景下,State 的核心问题是:

多个任务可能同时读写同一个 State,导致竞态(race)和不一致(inconsistency)。

例如 SwiftUI / TCA 中:

Task A: 用户点击加一
Task B: 网络返回初始值
Task C: 定时刷新

如果没有约束:

  • 最终状态可能不是任何一个动作的正确结果
  • UI 会显示“半更新”或“回退”
  • Bug 随机出现,很难复现

二、可选方案对比

方案思路优点缺点工程适用场景
锁(Lock / DispatchQueue.sync)用互斥保证一次只修改一个 State简单、直观容易死锁 / 阻塞 UI / 难扩展Legacy / 小型共享资源
Actor(Swift Concurrency)将 State 封装在 Actor 内,保证单线程访问自动序列化访问、线程安全、易组合Actor 内逻辑仍要小心不可阻塞推荐现代 Swift 并发架构
不可变数据 + CAS(copy-on-write)每次操作生成新 State,保证原始状态不变并发安全、支持时间旅行、快照测试内存占用大,复杂计算需优化Redux / TCA / 函数式风格
Reducer(单向数据流 + Action → State)所有修改通过 Reducer 串行化易测试、可回放、可组合、天然线程安全(配合 Actor / Store)需要严格遵循 Action → State 流程TCA / Redux / 大型复杂系统首选

三、工程分析

1️⃣ 锁

var state: Int = 0
let lock = NSLock()

func increment() {
    lock.lock()
    state += 1
    lock.unlock()
}
  • ✅ 简单修改安全
  • ❌ 分布式 Action / 异步任务会锁住 UI,容易死锁
  • ❌ 不可组合:多个锁容易互相阻塞

2️⃣ Actor(Swift 5.5+)

actor Counter {
    var value = 0
    func increment() { value += 1 }
}
  • ✅ 并发安全
  • ✅ 异步调用自动排队
  • ✅ 和 async/await 天然结合
  • ⚠️ 需要注意Actor 内不要调用阻塞操作

Actor 是现代 Swift 推荐方式,尤其配合 Reducer/Store。


3️⃣ 不可变数据 + Copy-on-Write

struct State {
    var items: [Item]
}

let newState = oldState.withUpdatedItem(at: 3, newItem: x)
  • ✅ 安全、可回放、时间旅行
  • ✅ 避免竞态
  • ❌ 内存占用大
  • ❌ 对大数组 / 深度对象需要 memoization 或优化

4️⃣ Reducer + 单向数据流

store.send(.add(item))
  • ✅ 所有状态变更经过唯一入口
  • ✅ 可组合、易测试
  • ✅ 可结合 Actor/Queue 实现并发安全
  • ✅ 派生 State 可从唯一 State 计算,保持一致性

核心思想:谁能改 State,只有 Reducer。Action 是唯一入口。


四、现代 Swift 工程倾向

Reducer + Actor + 不可变 State = 最优组合

  • Actor 或 DispatchQueue 串行化 State 修改
  • Reducer 作为唯一更新点,保证单向数据流
  • State 尽量不可变 / 派生 State 即时计算
  • UI 只观察 State,不直接改
  • 异步副作用通过 Effect / async Action 触发

TCA 就是这个思路的最佳实践:

Task {
    await store.send(.networkResponse(data))
}
  • store 内部 Actor 保证线程安全
  • Reducer 保证更新一致
  • 派生 State 计算自动更新 UI

五、总结一句话

State 一致性不是靠锁,而是靠“唯一修改入口 + 串行化 + 不可变/派生计算”保证。
在现代 Swift 并发架构里,Actor + Reducer + 不可变 State 是最稳、最可测试、最可扩展的组合。