⚡ 并发模型大全
一份从 理论到实践 的并发知识体系 —— 涵盖 20+ 种并发模型,按进程级 / 线程级 / I/O / 协程 / 事件驱动 / 消息传递 / 数据并行 七大维度分类, 包含原理讲解、代码示例、优劣分析和场景推荐。
📐 核心概念辨析
在深入并发模型之前,先区分几组容易混淆的概念。这是理解后续所有模型的地基。
并发 (Concurrency) vs 并行 (Parallelism)
并发:多个任务在同一时间段内交替执行,看起来像是同时进行,实际上 CPU 在多个任务之间快速切换。核心是逻辑上的同时。
并行:多个任务在同一时刻真正同时执行,需要多核 CPU 或多台机器。核心是物理上的同时。
同步 (Sync) vs 异步 (Async)
同步:调用方发起请求后,必须等待被调用方完成并返回结果,期间调用方线程被阻塞。
异步:调用方发起请求后立即返回,不等待结果。被调用方完成后通过回调、事件、Promise 等方式通知调用方。
阻塞 (Blocking) vs 非阻塞 (Non-blocking)
阻塞:调用后线程被挂起,直到操作完成才恢复。期间线程不能做任何其他事情。
非阻塞:调用后立即返回一个状态(如 EAGAIN / EWOULDBLOCK),线程可以继续执行其他任务,稍后再检查操作是否完成。
四者的关系矩阵
| 同步 Sync | 异步 Async | |
|---|---|---|
| 阻塞 Blocking | 经典阻塞 I/O(read/write 卡住) | 少见组合,通常无意义 |
| 非阻塞 Non-blocking | 非阻塞 I/O + 轮询 / I/O 多路复用 | 真正的异步 I/O(如 IOCP、io_uring) |
🖥️ 一、进程级并发
操作系统级别的并发单元。每个进程拥有独立的内存空间、文件描述符和系统资源,天然隔离,最安全但最重。
多进程 Multi-Process
利用操作系统的 fork() 或 spawn() 创建多个独立的进程,每个进程有独立的内存空间,通过 IPC(进程间通信)如管道、消息队列、共享内存、Socket 等进行数据交互。
✅ 优势
- 内存隔离,一个进程崩溃不影响其他
- 充分利用多核 CPU
- 安全性高,无数据竞争
❌ 劣势
- 创建和切换开销大(PCB 上下文切换)
- IPC 通信复杂且成本高
- 内存占用大,每个进程加载完整运行时
🎯 适用场景
CPU 密集型计算、需要高隔离性的服务(如 Chrome 多进程架构、Nginx Worker)、Python 中绕过 GIL# Python multiprocessing 示例 from multiprocessing import Process def worker(name): print(f"Worker {name} running") p1 = Process(target=worker, args=("A",)) p2 = Process(target=worker, args=("B",)) p1.start(); p2.start() p1.join(); p2.join()
进程池 Process Pool
预先创建固定数量的进程,任务到来时从池中取出空闲进程执行,完成后归还。避免频繁创建/销毁进程的开销。
✅ 优势
- 控制并发数量,防止资源耗尽
- 复用进程,减少创建销毁开销
❌ 劣势
- 池大小固定,峰值时可能排队
- 无法弹性伸缩
🎯 适用场景
Web 服务器的 Worker 模型(Apache prefork)、批量数据处理、Pythonconcurrent.futures.ProcessPoolExecutor🧵 二、线程级并发
进程内的轻量执行单元。多个线程共享进程的内存空间,通信成本低,但也带来了数据竞争和同步问题。
多线程 Multi-Threading
在同一个进程内创建多个线程,共享堆内存,各自拥有独立的栈和寄存器。操作系统负责线程调度(抢占式),时间片轮转。需要锁机制来保护共享数据。
✅ 优势
- 共享内存,通信成本几乎为零
- 创建/切换比进程轻量得多
- 充分利用多核
❌ 劣势
- 数据竞争(Race Condition)
- 死锁风险
- 调试困难(Heisenbug)
- Python 有 GIL、JS 单线程等语言限制
🎯 适用场景
I/O 密集型任务、GUI 程序(主线程负责 UI,Worker 线程负责计算)、Java/C++ 后端服务线程池 Thread Pool
预先创建 N 个线程放入池中,通过任务队列分配工作。核心思路:用少量线程处理大量任务,避免线程创建/销毁开销。
✅ 优势
- 控制资源上限,防止线程爆炸
- 线程复用,降低创建开销
❌ 劣势
- 核心线程数需要经验调优
- 任务阻塞会占用线程
🎯 适用场景
几乎所有生产级后端服务(Tomcat、Netty Worker Group、JavaThreadPoolExecutor、Python ThreadPoolExecutor)📡 三、I/O 模型
I/O 往往是现代应用的主要瓶颈。理解 I/O 模型是选择并发方案的关键。以下按照 Unix 网络编程的五种 I/O 模型展开。
同步阻塞 I/O Blocking I/O
应用调用 read(),如果内核数据未就绪,线程被挂起直到数据从内核拷贝到用户空间。这是最简单也最低效的模型。
🎯 适用场景
简单脚本、不需要并发的单连接程序、或配合多线程/多进程使用同步非阻塞 I/O Non-blocking I/O
将 socket 设为非阻塞模式,read() 立即返回。如果数据未就绪,返回错误码 EAGAIN,应用需要反复轮询直到数据就绪。CPU 空转严重,实际中很少单独使用。
I/O 多路复用 I/O Multiplexing
这是高性能网络服务的基石。一个线程同时监听多个 socket,当至少一个 socket 数据就绪时返回,然后逐个处理。核心思想:「一个线程管理多个连接」。
📊 select / poll / epoll 对比
| select | poll | epoll (Linux) | kqueue (BSD/macOS) | IOCP (Windows) | |
|---|---|---|---|---|---|
| fd 上限 | 1024(默认) | 无限制 | 无限制 | 无限制 | 无限制 |
| 扫描方式 | 全量遍历 O(n) | 全量遍历 O(n) | 事件驱动 O(1) | 事件驱动 O(1) | 完成端口 |
| 触发方式 | 水平触发 | 水平触发 | 水平+边缘触发 | 水平+边缘触发 | 完成通知 |
| 内核数据 | 每次传入全量 fd | 每次传入全量 fd | 事件表维护 | 事件表维护 | 端口绑定 |
🎯 适用场景
Nginx、Redis(单线程 + epoll)、Netty、Node.js 底层 libuv、几乎所有高性能网络框架异步 I/O Asynchronous I/O (AIO)
应用发起 aio_read() 后立即返回,内核在后台完成「等待数据 + 拷贝到用户空间」的全过程,完成后通过信号或回调通知应用。全程无阻塞。
🔧 主流异步 I/O 实现
- IOCP (Windows):最成熟的异步 I/O 实现,Proactor 模式的典范
- io_uring (Linux 5.1+):新一代异步 I/O,通过共享环形缓冲区实现零拷贝,性能远超 AIO
- POSIX AIO:Linux 上基于线程池模拟,不是真正的内核异步,性能一般
🎯 适用场景
高性能文件 I/O(数据库存储引擎)、Windows 高性能网络服务、使用 io_uring 的现代 Linux 应用🌀 四、协程 (Coroutine)
用户态的「轻量线程」,由程序自身调度而非操作系统。切换成本极低(纳秒级),可以在一个线程内实现数万并发。协程是解决 C10K/C100K 问题的用户态方案。
协程概述 Coroutine
协程是一种能在执行中主动让出(yield)控制权、之后再恢复(resume)的函数。与线程的抢占式调度不同,协程是协作式调度,切换点由程序员显式控制。
🏗️ 有栈协程 vs 无栈协程
| 有栈协程 (Stackful) | 无栈协程 (Stackless) | |
|---|---|---|
| 代表 | Go goroutine、Lua coroutine、Boost.Coroutine2 | Python asyncio、JS async/await、C++20 coroutine、Rust async |
| 实现 | 独立栈空间,可在任意嵌套函数中挂起 | 编译器将 async 函数转换为状态机,只能在这一层挂起 |
| 内存 | 每个协程 2~8KB 初始栈 | 仅保存局部变量,通常几十字节 |
| 灵活度 | 高,任意深度 yield | 受限于 async 函数边界 |
✅ 优势
- 切换成本极低(寄存器操作,无需陷入内核)
- 无锁(单线程内调度,无数据竞争)
- 可以支撑百万级并发
❌ 劣势
- 无法利用多核(需要配合多线程/多进程)
- CPU 密集型任务会阻塞整个调度器
- 调试和堆栈追踪困难
🎯 适用场景
高并发 I/O 密集型服务、API 网关、即时通讯、爬虫、微服务间调用async / await Async / Await 模式
现代语言(Python、JS/TS、Rust、C#、Dart、Swift)普遍采用的协程语法糖。用同步风格的代码写异步逻辑,大幅降低心智负担。
📝 各语言 async/await 对比
# Python async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as resp: return await resp.text() // JavaScript async function fetch(url) { const resp = await fetch(url); return await resp.text(); } // Rust async fn fetch(url: &str) -> Result<String> { let resp = reqwest::get(url).await?; resp.text().await }
🔄 五、事件驱动模型
以事件循环为核心,将 I/O 事件、定时器、用户操作等抽象为事件,通过回调或状态机处理。是现代 Web 服务器和 GUI 框架的基础。
事件循环 Event Loop
事件循环是一个永远运行的循环,持续从事件队列中取出事件并分发给对应的处理器。典型流程:收集事件 → 分发事件 → 处理事件 → 循环。
🏗️ 经典实现
- libevent / libev / libuv:跨平台事件循环库,Node.js 底层使用 libuv
- Redis 事件循环:自实现的 aeEventLoop,单线程 + epoll
- 浏览器事件循环:宏任务(setTimeout)+ 微任务(Promise)的两级队列
🎯 适用场景
Node.js 运行时、浏览器 JavaScript、Redis 单线程架构、GUI 框架(Qt、Electron)Reactor 模式 Reactor Pattern
Reactor 模式 = 事件循环 + I/O 多路复用 + 事件分发。核心是同步非阻塞地监听事件,然后同步地(或分发到线程池)处理事件。
Reactor 的三种线程模型
- 单 Reactor 单线程:Redis —— 简单高效,但一个 handler 阻塞会拖垮全局
- 单 Reactor 多线程:Handler 交给线程池,Reactor 线程只负责 I/O
- 主从 Reactor 多线程:Netty / Nginx —— MainReactor 负责 accept,SubReactor 负责读写和业务处理
Proactor 模式 Proactor Pattern
Proactor 更进一步:I/O 操作完全由内核异步完成,完成后通知应用。应用不需要参与数据读取过程,只需处理已经准备好数据的结果。
典型实现是 Windows IOCP 和 Linux io_uring。内核帮你把数据读到缓冲区,然后通知你「数据已经在这了,直接处理」。
🎯 适用场景
Windows 高性能服务(IIS、SQL Server)、Linux io_uring 的现代应用、需要极低 CPU 开销的 I/O 密集型系统📨 六、消息传递模型
不要通过共享内存来通信,而要通过通信来共享内存。这是 Go 语言的核心哲学,也是消息传递模型的基本理念。两大代表:CSP 和 Actor。
Communicating Sequential Processes
CSP 由 Tony Hoare 于 1978 年提出。核心概念:协程(goroutine)+ 通道(channel)。数据通过 channel 在 goroutine 间传递,channel 本身是同步点,天然保证数据安全。
// Go — CSP 的典范实现 func main() { ch := make(chan string) go func() { // goroutine(协程) time.Sleep(1 * time.Second) ch <- "hello" // 通过 channel 发送 }() msg := <-ch // 通过 channel 接收(阻塞直到有数据) fmt.Println(msg) } // select 多路复用 select { case msg1 := <-ch1: fmt.Println(msg1) case msg2 := <-ch2: fmt.Println(msg2) case <-time.After(1 * time.Second): fmt.Println("timeout") }
📊 CSP vs Actor 关键区别
| CSP | Actor | |
|---|---|---|
| 通信方式 | 匿名 channel,收发双方不需要知道对方身份 | 命名 Actor,直接向指定 Actor 的 mailbox 发消息 |
| 耦合度 | 低(通过 channel 解耦) | 较高(需要知道目标 Actor 地址) |
| 同步性 | 无缓冲 channel = 同步;有缓冲 = 异步 | 消息发送是异步的 |
| 代表 | Go、Clojure core.async、Crystal | Erlang/Elixir、Akka(Scala/Java)、Orleans(C#) |
✅ 优势
- 无锁设计,通过 channel 保证同步
- 代码简洁,goroutine 极轻量(~2KB)
select提供优雅的多路复用和超时控制
❌ 劣势
- channel 使用不当会导致 goroutine 泄漏
- 分布式场景下 channel 不适用(需借助 RPC/消息队列)
🎯 适用场景
Go 后端微服务、网络编程、需要高并发的任何场景(Go 生态)Actor 模型
Actor 模型由 Carl Hewitt 于 1973 年提出。每个 Actor 是一个独立计算单元,有自己的状态和 mailbox(消息队列)。Actor 之间完全通过异步消息通信,不共享任何内存。
🔑 三大核心原则
- 一切皆 Actor
- Actor 有自己的私有状态,外部无法直接访问
- 通信只能通过异步消息,没有共享内存
✅ 优势
- 天然分布 —— Actor 可以分布在多台机器
- 容错性强 —— Erlang OTP 的 Let It Crash 哲学,Supervisor 树自动恢复
- 无锁、无共享、无数据竞争
❌ 劣势
- 消息是异步的,执行顺序不确定
- 数据拷贝开销(消息需序列化)
- 调试和追踪困难
🎯 适用场景
高可用通信系统(WhatsApp 用 Erlang)、游戏服务器、IoT 消息处理、需要天然分布和容错的系统🗂️ 七、数据并行模型
将大数据集切分为小块,分配给多个 worker 并行处理,最后汇总结果。适用于计算密集的批量数据处理场景。
MapReduce
Google 2004 年提出的大规模数据处理范式。分两个阶段:Map(将输入数据映射为键值对中间结果)→ Shuffle(按 key 分组)→ Reduce(对每组数据聚合)。
🎯 适用场景
Hadoop、大规模日志分析、倒排索引构建、分布式排序、ETL 数据处理Fork / Join
将大任务递归地Fork(拆分)为小任务并行执行,最后Join(合并)结果。核心数据结构是工作窃取(Work-Stealing)双端队列,空闲线程从繁忙线程的队列尾部偷任务,实现负载均衡。
// Java ForkJoinPool — 递归求和 class SumTask extends RecursiveTask<Long> { protected Long compute() { if (high - low <= THRESHOLD) { return directSum(low, high); } mid = (low + high) / 2; left = new SumTask(low, mid).fork(); // 异步拆分 right = new SumTask(mid, high).fork(); return left.join() + right.join(); // 等待并合并 } }
🎯 适用场景
递归算法并行化(归并排序、快速排序)、并行流(Java Stream.parallel())、图像处理、科学计算🔬 八、其他并发范式
一些独特或组合式的并发模式,在实践中同样非常重要。
单线程异步 Single-Threaded Async
以 Node.js 为典型代表:一个主线程 + 事件循环 + 异步非阻塞 I/O。主线程处理所有 JavaScript 代码,I/O 操作委托给 libuv 线程池或操作系统异步接口,完成后通过回调/事件返回主线程。
// Node.js — 单线程异步 const fs = require('fs'); const http = require('http'); // 非阻塞读取,完成后回调 fs.readFile('large.txt', (err, data) => { // 这个回调稍后在事件循环中执行 console.log(data.length); }); console.log('我先输出!'); // 这行先执行
✅ 优势
- 无锁、无并发 bug
- 编程模型简单(回调/Promise/async-await)
- 内存开销低
❌ 劣势
- CPU 密集任务会阻塞整个进程
- 不能利用多核(需要 cluster/worker_threads)
🎯 适用场景
I/O 密集的 Web 服务、API 网关、实时应用(Socket.io)、轻量微服务响应式编程 Reactive Programming
以数据流(Stream)和变化传播为核心。将一切视为异步数据流,通过声明式操作符(map、filter、flatMap)组合和变换流。天然支持背压(Backpressure),即消费者可以通知生产者放慢速度。
代表:ReactiveX(RxJava、RxJS、RxSwift)、Project Reactor(Spring WebFlux 底层)、Akka Streams
🎯 适用场景
实时数据管道、UI 事件处理、股票行情推送、IoT 传感器数据流、微服务间的响应式通信STM Software Transactional Memory
借鉴数据库事务思想,将对共享内存的读写包装为原子事务。事务内对共享变量的修改对其他线程不可见,提交时检测冲突,有冲突则回滚重试。代表:Clojure STM、Haskell STM、C++ 实验性实现。
✅ 优势
- 无锁、无死锁(由 STM 引擎处理冲突)
- 组合性好 —— 多个操作可以组合成一个大事务
❌ 劣势
- 性能开销大(冲突重试)
- 不适合 I/O 操作
- 未成为主流,生态有限
🎯 适用场景
复杂的共享状态管理、需要事务语义的内存操作、Clojure 生态中的状态管理流水线并发 Pipeline Concurrency
将处理流程拆分为多个阶段(Stage),每个阶段在独立的线程/进程中运行,阶段之间通过队列传递数据。类似于工厂流水线,多个物品可以在不同阶段同时处理。
🎯 适用场景
视频/音频编解码管道、ETL 数据处理、编译器前端(词法分析 → 语法分析 → 代码生成)、GPU 渲染管线📊 全景对比表
将所有并发模型放在一张表中横向对比,快速了解各模型的核心特征。
| 模型 | 调度层级 | 通信方式 | 是否共享内存 | 并发单元开销 | 适合 I/O 密集 | 适合 CPU 密集 | 代表性实现 |
|---|---|---|---|---|---|---|---|
| 多进程 | OS(内核态) | IPC(管道/共享内存/Socket) | ❌(隔离) | 高(MB 级) | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Nginx Worker、Python multiprocessing |
| 多线程 | OS(内核态) | 共享内存 + 锁 | ✅ | 中(MB 级栈) | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | Java Thread、pthread |
| 线程池 | OS(内核态) | 共享内存 + 任务队列 | ✅ | 中 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ThreadPoolExecutor、ForkJoinPool |
| I/O 多路复用 | OS(内核通知) | 由上层决定 | 视上层 | 低(fd 注册) | ⭐⭐⭐⭐⭐ | ⭐ | epoll、kqueue、IOCP |
| 异步 I/O (io_uring) | 内核异步 | 共享环形缓冲区 | ✅ | 极低 | ⭐⭐⭐⭐⭐ | ⭐⭐ | Linux io_uring |
| 协程 | 用户态协同 | 共享内存(单线程内) | ✅ | 极低(KB 级) | ⭐⭐⭐⭐⭐ | ⭐ | Python asyncio、Lua coroutine |
| goroutine (CSP) | 用户态 + 抢占 | Channel | ❌(通过 channel) | 极低(~2KB) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Go |
| 虚拟线程 | JVM 用户态 | 共享内存 | ✅ | 极低(堆外分配) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | Java 21 Virtual Threads (Loom) |
| 事件循环 (单线程异步) | 用户态事件循环 | 回调/Promise | ✅ | 极低 | ⭐⭐⭐⭐⭐ | ⭐ | Node.js、浏览器 JS |
| Actor 模型 | 用户态 | 异步消息 | ❌ | 低 | ⭐⭐⭐⭐ | ⭐⭐⭐ | Erlang/OTP、Akka |
| MapReduce | 分布式框架 | HDFS + Shuffle | ❌ | 高(集群) | ⭐⭐ | ⭐⭐⭐⭐⭐ | Hadoop、Spark |
| Fork / Join | OS 线程池 | 共享内存 + Work-Stealing | ✅ | 中 | ⭐⭐ | ⭐⭐⭐⭐⭐ | Java ForkJoinPool、Cilk |
| 响应式编程 | 事件循环 | 异步流 | 视底层 | 低 | ⭐⭐⭐⭐⭐ | ⭐⭐ | Reactor、RxJava |
| STM | 用户态 | 事务内存 | ✅ | 中 | ⭐⭐ | ⭐⭐⭐ | Clojure ref、Haskell STM |
🎯 选型决策指南
面对具体业务场景时,按以下决策树快速定位合适的并发模型。
决策树:我需要什么并发模型?
🔵 场景一:高并发 I/O 密集型服务(Web 服务、API 网关、代理)
首选:Go (goroutine + channel) 或 Java 21 虚拟线程。
备选:Node.js(单线程异步)、Rust async(极致性能)、Erlang/Elixir(高可用需求)。
不建议:传统多线程(C10K 问题)、Python asyncio(CPU 受限)。
🟢 场景二:CPU 密集型计算(科学计算、图像处理、ML 推理)
首选:多进程 或 Fork/Join 线程池。
备选:GPU 并行(CUDA)、MapReduce(大规模分布式)。
不建议:单线程事件循环、协程(单线程阻塞问题)。
🟡 场景三:混合负载(既要高并发 I/O,又偶尔有 CPU 密集任务)
首选:Go —— goroutine 调度器会自动在 OS 线程间迁移,CPU 密集任务不会饿死 I/O。
备选:Node.js + Worker Threads、Python asyncio + ProcessPoolExecutor。
模式:异步主线程处理 I/O,CPU 任务隔离到专用线程/进程池。
🟠 场景四:需要强一致性的共享状态(金融交易、库存扣减)
首选:单线程事件循环(如 Redis 单线程模型) —— 天然串行化,无竞争。
备选:Actor 模型(通过消息串行化)、STM(事务语义)。
不建议:多线程 + 细粒度锁(死锁地狱)。
🔴 场景五:分布式 + 高可用系统
首选:Actor 模型(Erlang/Elixir) —— 天然支持分布式、容错和热更新。
备选:Go + 消息队列(Kafka/NATS)模拟消息传递。
核心:用消息传递替代 RPC 直接调用,实现松耦合和故障隔离。
⚪ 场景六:前端 / GUI 应用
首选:事件循环 + 响应式编程。
UI 线程(事件循环)保持响应,耗时操作放入 Worker 线程,通过消息/事件通信。
代表:浏览器 JS、Flutter (Isolate)、Android (Handler/Looper)、iOS (GCD)。
💡 核心原则总结
- I/O 密集 → 用异步(协程 / 事件循环 / goroutine),不要让线程在 I/O 上白白等待
- CPU 密集 → 用多核(多进程 / Fork-Join / GPU),让每个核心都跑满
- 共享状态复杂 → 用消息传递(CSP / Actor),不要通过共享内存来通信
- 高可用需求 → 用 Actor(Let It Crash + Supervisor 树),容错是第一优先级
- 简单优先 → 单线程异步足够时不要上多线程,复杂度往往比性能损耗更昂贵
- 没有银弹 —— 现代系统通常是多种模型的组合(如 Rust:tokio 异步运行时 + rayon 数据并行 + channel 消息传递)