读书笔记 · 约 11,972 字 · 预计推送 2 部分

主题精读:并发与异步编程——线程、进程、async/await、Actor、消息队列的区别 未开始

目录

题记与说明

- 日期:2026-11-01
- 前缀:knowledge-v_9xs19r-0
- 类型:主题研究(网络可用,共精读 6 个来源:Jenkov 并发模型教程、Rust Async Book、Akka 官方文档、Python asyncio 官方文档、Python multiprocessing 官方文档、Apache Kafka 官方介绍)
- 来源清单:
1. Jakob Jenkov, *Concurrency Models* — https://tutorials.jenkov.com/java-concurrency/concurrency-models.html (全文精读)
2. *Asynchronous Programming in Rust* — *Why Async?* — https://rust-lang.github.io/async-book/01_getting_started/02_why_async.html (全文精读)
3. Akka 官方文档 *What is an Actor?* — https://doc.akka.io/libraries/akka-core/current/general/actors.html (重点摘读)
4. Python 官方文档 *asyncio* — https://docs.python.org/3/library/asyncio.html (重点摘读)
5. Python 官方文档 *multiprocessing* — https://docs.python.org/3/library/multiprocessing.html (重点摘读)
6. Apache Kafka, *Introduction* — https://kafka.apache.org/intro (重点摘读)

一、核心问题

这五种机制常被混称为"并发",但它们回答的是不同层面的问题:

  1. 线程 vs 进程:操作系统级的执行单元——共享内存还是相互隔离?切换成本多大?谁能用上多核?
  2. async/await:当线程数量远撑不起任务数量(成千上万并发 IO)时,如何用少量 OS 线程调度海量任务,同时保留接近同步代码的可读性?
  3. Actor 模型:如何从根上消灭共享内存竞态——把"状态 + 计算单元"封装起来,只允许消息传递?
  4. 消息队列:当通信双方跨越进程/机器/生命周期边界时,如何解耦生产者与消费者,并取得缓冲、削峰、持久化与重放能力?

Jenkov 开篇的定义是全篇的锚点:

"A concurrency model specifies how threads in the system collaborate to complete the tasks they are given. Different concurrency models split the tasks in different ways, and the threads may communicate and collaborate in different ways."
(并发模型规定的是线程之间如何分工与协作,不同模型切分任务与通信的方式不同。)

一句话区分:线程/进程是"执行资源",async/await 是"单线程内的调度方式",Actor 是"架构模式(隔离状态 + 消息)",消息队列是"跨边界的中间件基础设施"——四者不在同一抽象层,因此常常是组合使用而非互斥选择。


二、主要主张(分来源)

1. Jenkov:并发模型的两条主线——"并行工人" vs "流水线"

"When no two threads write to the same object (data / state), you can avoid most of the common concurrency problems."(只要没有两个线程写同一份数据,大多数并发问题就消失了。)
"Personally I prefer separate state concurrency designs."
"High contention will essentially lead to a degree of serialization of execution (eliminating parallelization) of the part of the code that access the shared data structures."

(对共享数据结构的争用会把并行实际串行化;非阻塞算法难写,持久化数据结构性能不佳——对底层硬件是 "a lot higher performance out of a list implemented on top of an array",链式结构毁掉 CPU cache 局部性。)

2. Rust Async Book:async/await 与 OS 线程的正式对比

"async ... is a concurrent programming model ... It lets you run a large number of concurrent tasks on a small number of OS threads, while preserving much of the look and feel of ordinary synchronous programming."
"asynchronous programming is not *better* than threads, but different. If you don't need async for performance reasons, threads can often be the simpler alternative."

3. Akka:Actor 模型的严格定义

"The processing units—Actors—can only communicate by exchanging messages."
"it is not possible to look inside an actor and get hold of its state from the outside."(外部不可能窥视 actor 内部状态——封装是模型强制的,不是纪律建议。)

4. Python asyncio:async/await 的官方表述

"asyncio is a library to write concurrent code using the async/await syntax."
"asyncio is often a perfect fit for IO-bound and high-level structured network code."

5. Python multiprocessing:进程维度的官方表述

"The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads."
"Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine."

6. Kafka:消息队列(事件流)在分布式层的定位

"In Kafka, producers and consumers are fully decoupled and agnostic of each other, which is a key design element to achieve the high scalability."
"producers never need to wait for consumers."

三、方法

主题式综述的方法路径:

  1. 选权威一手来源:每个概念都对应其官方文档或经典教程(Rust 官方 Async Book 给出四种模型对比;Akka 给出 Actor 权威定义;Python 官方文档给 asyncio 与 multiprocessing 的准确定位;Kafka 官方介绍给消息队列的分布式定位;Jenkov 教程提供跨模型的统一框架)。
  2. 统一分析框架:按 Jenkov 的框架沿三个正交维度对齐五个对象——①状态是否共享(shared vs separate state);②调度方式(抢占式 vs 协作式/消息驱动);③边界(同进程内存 / 跨进程 / 跨机器)。
  3. 引述优先:每个结论保留原文引述,避免二手转述失真。

四、数据与证据

五机制对照表(综合六来源)

维度线程 Thread进程 Processasync/awaitActor消息队列 MQ
所在层OS 内核调度OS 内核 + 地址空间隔离语言运行时/事件循环(如 asyncio、Tokio)应用架构模式(Akka/Erlang/Swift actor)跨网络中间件(Kafka/RabbitMQ)
状态共享内存,需锁各自独立,需 IPC单线程协作式,天然免竞态(仍需防跨线程共享)强制隔离:"cannot look inside"双方完全解耦,经持久化事件交互
调度抢占式抢占式协作式(await 为让出点)事件驱动:每 actor 串行处理邮箱异步拉取/推送
适用负载少量并发、CPU-bound、复用既有同步代码CPU-bound、真并行(绕过 GIL)、强隔离海量 IO-bound(服务器、网络客户端)有状态会话、需容错与监督的领域建模跨服务解耦、削峰、持久化重放
主要代价同步难、切换贵;"not enough to support massive IO-bound workloads"创建/切换贵、需序列化二进制变大、runtime 依赖、侵入函数签名("major refactoring")"flow control and retry logic" 要自己补引入独立组件与运维成本
典型失败模式数据竞争、死锁、竞态内存开销、IPC 序列化瓶颈阻塞事件循环(在协程里做同步 IO)死信、邮箱堆积消息积压、重复消费需幂等
可否组合与 async(runtime 底层就是线程池)、与 Actor(Akka 底层线程池)、与 MQ(worker 消费)皆可是 async 的 IO 后备(asyncio "control subprocesses")是 Actor/MQ 客户端的常见载体可跑在 async 或线程池上消费端往往是进程/协程池

支撑引述的证据链


五、结论

  1. 五者是不同抽象层,常组合而非互斥:OS 给进程与线程;async/await 在"单个线程"内做协作式多任务;Actor 是"线程/协程之上"的架构约束(状态隔离 + 邮箱串行);消息队列是"进程边界之外"的分布式基础设施。
  2. 选型的第一分叉是负载类型:CPU-bound → 进程(真并行,Python 下即绕过 GIL);少量并发/大量现成同步代码 → 线程更简单("threads can often be the simpler alternative");海量 IO-bound → async/await;有状态且要容错 → Actor;跨服务/跨生命周期/要重放 → 消息队列。
  3. 所有高级模型的共同思想是"不共享可变状态":Actor 强制、async 单线程天然、消息队列用持久化事件彻底外移。Jenkov 断言多数系统更适合流水线式(separate state)模型,shared state 的并行工人模型"surface simple, disadvantages lurking under the surface"。
  4. Actor 与消息队列的边界:Actor 解决进程内/集群内的计算组织(但 "leaves many practical issues unanswered, such as flow control and retry logic");消息队列补的正是这些——持久化、重放、削峰、跨语言跨机器。二者在分布式系统里常叠用。
  5. 转换成本是选型的一部分:线程↔async 迁移"requires major refactoring work",决定要尽早做。

六、局限


七、对我的启发

结合本工作区此前的条目(本地 LLM 推理引擎、事件驱动系统与消息队列、ripgrep 并行搜索、慢思考与测试时计算),可沉淀为一套并发选型检查表:

  1. Agent/LLM 工具编排默认用 async/await:LLM API 调用、子进程调用、工具 IO 全是 IO-bound,海量并发请求正是 async 的主场;但要警惕在协程里调用同步阻塞库(如本地推理 CLI)会把事件循环整个卡住——需要时丢给 asyncio.to_thread 或线程池。
  2. CPU-bound 一律下沉到进程:文档解析、embedding 批处理、索引构建这类 Python 代码按 multiprocessing 文档的结论应走子进程(绕 GIL),而 Windows 上进程创建开销大,应当用进程池常驻而非每次 spawn。
  3. 把"会话状态"按 Actor 组织:多轮对话、任务状态机这类有状态单元,与其用锁保护全局 dict,不如每个会话一个 actor(串行处理消息、状态只属于自己)——这正是 Akka 文档 "cannot look inside an actor" 的价值;Python 里可用单事件循环 + 每会话队列廉价模拟。
  4. 消息队列只在边界处引入:跨进程/跨机/跨生命周期才值得付出 MQ 的运维成本;进程内异步任务分发用 asyncio.Queue(官方列出的 "distribute tasks via queues")即可。之前精读的事件驱动条目(Kafka vs RabbitMQ vs NATS)与本条互补:本条回答"为什么解耦",那条回答"选哪个队列"。
  5. 写作/教学切入点:五模型最清晰的讲法不是逐个介绍,而是沿"共享状态吗?谁来调度?边界在哪?"三问展开——Jenkov 与 Rust book 的框架已经证明这条路径的效率。
  6. 一个反直觉提醒:async 不是"更高级的线程"。Rust book 的最后一句值得贴在显示器上——"asynchronous programming is not *better* than threads, but different"。

*精读完成于 2026-11-01;全部引述来自上列 6 个来源在本次会话中的实际抓取内容(Jenkov 与 Rust Async Book 为全文,其余为官方文档重点摘读)。*

← 资料库今日播报 →