题记与说明
- 日期: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 (重点摘读)
一、核心问题
这五种机制常被混称为"并发",但它们回答的是不同层面的问题:
- 线程 vs 进程:操作系统级的执行单元——共享内存还是相互隔离?切换成本多大?谁能用上多核?
- async/await:当线程数量远撑不起任务数量(成千上万并发 IO)时,如何用少量 OS 线程调度海量任务,同时保留接近同步代码的可读性?
- Actor 模型:如何从根上消灭共享内存竞态——把"状态 + 计算单元"封装起来,只允许消息传递?
- 消息队列:当通信双方跨越进程/机器/生命周期边界时,如何解耦生产者与消费者,并取得缓冲、削峰、持久化与重放能力?
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 "流水线"
- 并发系统最关键的维度是共享状态 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."
- Parallel Workers 模型(经典多线程/线程池,如 Java EE、
java.util.concurrent):- 优点:"it is easy to understand. To increase the parallelization level of the application you just add more workers."(简单直观,加工人即可加并行度。)
- 缺点一,共享状态复杂化:
"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 局部性。)
- 缺点二,job ordering 非确定:"Job A may be given to a worker before job B, yet job B may be executed before job A."——难以推理系统在任意时刻的状态。
- Assembly Line(流水线)模型(reactive / event-driven,如 Vert.x、Akka、Node.js):非阻塞 IO 决定了工人的职责边界——工人做到需要 IO 处就让出控制权,IO 完成后结果交给下一个工人。其优点:无共享状态、工人可以有状态(内存缓存 + CPU cache 命中,即 "hardware conformity" / "mechanical sympathy")、可保证 job order(写入日志即可重放重建状态)。缺点:单个 job 的代码分散在多个工人/类里,回调风格易陷入 "callback hell"。
- Actor vs Channel 的区分:Actor 直接互发消息;Channel 模型里工人把消息发到频道,发送者不知道谁在听——"Listeners on channels can subscribe and unsubscribe without affecting the workers writing to the channels. This allows for a somewhat looser coupling."(消息队列正是 channel 思想的分布式化。)
2. Rust Async Book:async/await 与 OS 线程的正式对比
- async 的定义:
"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."
- 四种并发模型的官方对照(原文要点):
- OS 线程:不改编程模型、最易表达并发,但 "synchronizing between threads can be difficult, and the performance overhead is large. Thread pools can mitigate some of these costs, but not enough to support massive IO-bound workloads."
- 事件驱动 + 回调:性能好,但 "tends to result in a verbose, 'non-linear' control flow"(即 callback hell 的学术版)。
- 协程:像线程一样好用、可支持海量任务,但 "abstract away low-level details that are important for systems programming"。
- Actor 模型:"communicate through fallible message passing, much like in distributed systems. The actor model can be efficiently implemented, but it leaves many practical issues unanswered, such as flow control and retry logic."(Actor 高效,但流控、重试等实际问题要自己补——这些正是消息队列在分布式层的强项。)
- 量级差异:async 任务 "orders of magnitude more tasks than OS threads",因为 runtime 用少量贵的线程调度大量便宜的任务;代价是二进制变大(async fn 生成状态机 + 打包 runtime)。
- 决策口诀(原文最后一句):
"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."
- 迁移成本警告:"Migrating from threads to async or vice versa typically requires major refactoring work",技术选型要趁早。
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 内部状态——封装是模型强制的,不是纪律建议。)
- Actor = "a container for State, Behavior, a Mailbox, Child Actors and a Supervisor Strategy"。
- 免锁推导:"instead of having to synchronize access using locks you can write your actor code without worrying about concurrency at all"——每个 actor 串行处理自己邮箱里的消息,天然单线程语义。
- 邮箱语义:"each actor has exactly one mailbox to which all senders enqueue their messages",默认 FIFO,且 "there is no scanning the mailbox for the next matching one"(不能跳过不匹配的消息——与 select/epoll 的"多路监听"形成对比)。
- 两个分布式特性:位置透明(actor 引用可指向远程主机上的实例)与监督/自愈(父 actor 自动监督子 actor,失败重启后 "the state will be created from scratch",实现 "self-healing of the system")。
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."
- 高层 API 的能力清单:并发跑协程、网络 IO 与 IPC、控制子进程、用队列分发任务、同步并发代码——注意官方也内置了异步队列,说明"队列"思想在单进程 async 世界同样存在。
- 事件循环是底层核心:"create and manage event loops, which provide asynchronous APIs for networking, running subprocesses, handling OS signals"。
- 官方页面上协程的用法(
async def main()+await asyncio.sleep(1)+asyncio.run(main()))表明:await 是显式让出点——协程在 await 处挂起、事件循环接管,这正是"协作式调度"与线程"抢占式调度"的本质区别。
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."
- 进程与线程 API 几乎同构("mostly replicates the API of the threading module"),差异在于两点:隔离与代价——进程各有独立内存空间、独占 GIL,因而能真并行 CPU 密集任务;但也因此多了进程创建/切换开销与序列化通信(Pipe/Queue)成本,且 "introduces APIs which do not have analogs in the threading module, like the ability to terminate, interrupt or kill a running process"(进程可以强杀,隔离带来更强的故障处置手段)。
Pool提供数据并行:"parallelizing the execution of a function across multiple input values, distributing the input data across processes (data parallelism)."
6. Kafka:消息队列(事件流)在分布式层的定位
- 本质是事件流平台:"to publish (write) and subscribe to (read) streams of events ... To store streams of events durably and reliably for as long as you want."
- 解耦的严格表述:
"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."
- 与内存队列的两大差别:持久化与重放("events are not deleted after consumption",按保留策略留存,性能 "effectively constant with respect to data size")与分区有序(同一 key 写同一分区,分区内按写入顺序读取——这是 Jenkov 所说"流水线可保证 job ordering"在分布式层的实现)。
- 容错:topic 可复制("A common production setting is a replication factor of 3"),broker 宕机自动接管 "without any data loss"。
三、方法
主题式综述的方法路径:
- 选权威一手来源:每个概念都对应其官方文档或经典教程(Rust 官方 Async Book 给出四种模型对比;Akka 给出 Actor 权威定义;Python 官方文档给 asyncio 与 multiprocessing 的准确定位;Kafka 官方介绍给消息队列的分布式定位;Jenkov 教程提供跨模型的统一框架)。
- 统一分析框架:按 Jenkov 的框架沿三个正交维度对齐五个对象——①状态是否共享(shared vs separate state);②调度方式(抢占式 vs 协作式/消息驱动);③边界(同进程内存 / 跨进程 / 跨机器)。
- 引述优先:每个结论保留原文引述,避免二手转述失真。
四、数据与证据
五机制对照表(综合六来源)
| 维度 | 线程 Thread | 进程 Process | async/await | Actor | 消息队列 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 或线程池上 | 消费端往往是进程/协程池 |
支撑引述的证据链
- 量级差异(线程 vs async 任务数):"orders of magnitude more tasks than OS threads" —— Rust Async Book。
- 单线程语义(Actor 免锁的根本):"you can write your actor code without worrying about concurrency at all" —— Akka。
- 进程真并行的原因:"side-stepping the Global Interpreter Lock by using subprocesses" —— Python multiprocessing 文档。
- 解耦的收益:"producers never need to wait for consumers" + "events are not deleted after consumption" —— Kafka 介绍。
- 共享状态的代价:"High contention will essentially lead to a degree of serialization of execution" —— Jenkov。
- 模型等价性(并发模型≈分布式架构):"Because concurrency models are similar to distributed system architectures, they can often borrow ideas from each other." —— Jenkov(这也是 Actor 与 MQ 外形相似的根源)。
五、结论
- 五者是不同抽象层,常组合而非互斥:OS 给进程与线程;async/await 在"单个线程"内做协作式多任务;Actor 是"线程/协程之上"的架构约束(状态隔离 + 邮箱串行);消息队列是"进程边界之外"的分布式基础设施。
- 选型的第一分叉是负载类型:CPU-bound → 进程(真并行,Python 下即绕过 GIL);少量并发/大量现成同步代码 → 线程更简单("threads can often be the simpler alternative");海量 IO-bound → async/await;有状态且要容错 → Actor;跨服务/跨生命周期/要重放 → 消息队列。
- 所有高级模型的共同思想是"不共享可变状态":Actor 强制、async 单线程天然、消息队列用持久化事件彻底外移。Jenkov 断言多数系统更适合流水线式(separate state)模型,shared state 的并行工人模型"surface simple, disadvantages lurking under the surface"。
- Actor 与消息队列的边界:Actor 解决进程内/集群内的计算组织(但 "leaves many practical issues unanswered, such as flow control and retry logic");消息队列补的正是这些——持久化、重放、削峰、跨语言跨机器。二者在分布式系统里常叠用。
- 转换成本是选型的一部分:线程↔async 迁移"requires major refactoring work",决定要尽早做。
六、局限
- 来源视角偏后端/系统语言:六来源分别站 JVM(Jenkov、Akka)、Rust、Python、Kafka 立场,未覆盖 Go(goroutine/channel:介于协程与 channel 模型之间的第三条路)、Java Loom(虚拟线程正让"海量线程"重新可行,部分削弱 async 的优势)、Erlang/Elixir OTP 原文(Actor 的"let it crash"哲学没有第一手引述)。
- 缺定量基准:各文档只给定性结论(如 "orders of magnitude"),本笔记未跑任何 benchmark 验证线程 vs 协程 vs 进程的实际吞吐差异(如 C10K、上下文切换纳秒级开销)——Rust book 自己也承认 async 的代价是二进制变大与 runtime 选择负担。
- 消息队列一节偏 Kafka:RabbitMQ 官方 queues 页(已读)未给出解耦论证,改用 Kafka 补足;RabbitMQ 的 AMQP 语义、反压(flow control)机制未精读。
- Actor 的短板是转述:"flow control and retry logic" 未被解答的判断来自 Rust book 对 Actor 的评述,而非 Erlang/Akka 社区的自我评价。
- 平台差异未展开:Windows 上 IOCP 与 Linux epoll 的事件循环实现差异、GIL 在 free-threaded Python(3.13+)下的变化,官方文档仅以链接带过,未深入。
七、对我的启发
结合本工作区此前的条目(本地 LLM 推理引擎、事件驱动系统与消息队列、ripgrep 并行搜索、慢思考与测试时计算),可沉淀为一套并发选型检查表:
- Agent/LLM 工具编排默认用 async/await:LLM API 调用、子进程调用、工具 IO 全是 IO-bound,海量并发请求正是 async 的主场;但要警惕在协程里调用同步阻塞库(如本地推理 CLI)会把事件循环整个卡住——需要时丢给
asyncio.to_thread或线程池。 - CPU-bound 一律下沉到进程:文档解析、embedding 批处理、索引构建这类 Python 代码按 multiprocessing 文档的结论应走子进程(绕 GIL),而 Windows 上进程创建开销大,应当用进程池常驻而非每次 spawn。
- 把"会话状态"按 Actor 组织:多轮对话、任务状态机这类有状态单元,与其用锁保护全局 dict,不如每个会话一个 actor(串行处理消息、状态只属于自己)——这正是 Akka 文档 "cannot look inside an actor" 的价值;Python 里可用单事件循环 + 每会话队列廉价模拟。
- 消息队列只在边界处引入:跨进程/跨机/跨生命周期才值得付出 MQ 的运维成本;进程内异步任务分发用
asyncio.Queue(官方列出的 "distribute tasks via queues")即可。之前精读的事件驱动条目(Kafka vs RabbitMQ vs NATS)与本条互补:本条回答"为什么解耦",那条回答"选哪个队列"。 - 写作/教学切入点:五模型最清晰的讲法不是逐个介绍,而是沿"共享状态吗?谁来调度?边界在哪?"三问展开——Jenkov 与 Rust book 的框架已经证明这条路径的效率。
- 一个反直觉提醒:async 不是"更高级的线程"。Rust book 的最后一句值得贴在显示器上——"asynchronous programming is not *better* than threads, but different"。
*精读完成于 2026-11-01;全部引述来自上列 6 个来源在本次会话中的实际抓取内容(Jenkov 与 Rust Async Book 为全文,其余为官方文档重点摘读)。*