🧠 并发 & 并行
📑 目录
1️⃣ 并发 vs 并行:核心差异
🏷️ 维度 | ⚡ 并发 Concurrency | 🚀 并行 Parallelism |
---|---|---|
🔍 关注点 | 任务管理:把多任务切成片,交错推进 | 执行层面:让多任务在多个处理单元上“真的”同时运行 |
🧬 本质 | 时间片重叠 → 潜在同时 | 空间并列 → 物理同时 |
🔑 必要条件 | 只要有调度器,单核也能并发 | 至少 2 个核心、GPU 或多机 |
⭐ 典型特征 | 上下文切换、共享状态需同步、次序非确定 | 性能随核心数线性扩缩(受 Amdahl/Gustafson 限制) |
🎯 一句话 | 任务管理的抽象方式 | 并发在硬件上的落地实践 |
记忆口诀:并发管“怎么排队”,并行管“怎么同时”。
2️⃣ 关键概念词典
表头说明
- 标准定义:教科书级表述,严谨不跑偏
- 通俗解释:给小白讲故事
- 典型使用场景:日常代码里在哪见得到
图标 | 术语 | 标准定义 | 通俗解释 | 典型使用场景 |
---|---|---|---|---|
🔄 | 上下文切换 Context Switch | CPU 将执行权从线程 A 切给线程 B 时,需保存/恢复寄存器、栈指针及程序计数器的过程。 | CPU 换节目单:把 A 的进度“拍照存档”,再拿出 B 的“照片”继续播。 | OS 抢占调度;高频 I/O 服务器;多线程 GUI |
🔒 | 互斥锁 Mutex | 一种同步原语:任一时刻仅允许一个执行单元进入临界区,避免并发写冲突。 | 一把独门钥匙——谁拿到谁进屋,其他人排队。 | 共享哈希表、更新全局计数器、文件写入 |
🌐 | 信号量 Semaphore | 带计数器的同步机制,支持同时放行 N 个并发进入临界区。 | 多车道红绿灯:计数=车道数,牌子剩几就能进几辆。 | 数据库连接池、线程池工作槽、限流 |
⚙️ | 原子操作 Atomic | 不可中断的单条指令序列,操作期间对其他线程“不可见”。 | “拿起→修改→放下”一步到位,别人插不进队。 | 写无锁计数器、实现自旋锁、并行累加 |
✉️ | Channel / Queue | 生产者与消费者之间的有序数据通道,不共享内存而传递消息。 | 快递箱:一头塞包裹,一头拆包裹。 | Go chan 、Rust mpsc 、线程间任务队列 |
📮 | Mailbox | Actor 私有的消息队列,保证单线程顺序处理。 | 专属信箱:只收自己的信,一封一封读。 | Erlang/Elixir、Akka、Orleans |
🚦 | 调度器 Scheduler | 系统或运行时根据算法为任务分配 CPU 的逻辑组件。 | 交通警察:指挥哪辆车先走。 | Linux CFS、Go M:N 调度器、Tokio Runtime |
⚖️ | 负载均衡 Load Balancer | 将请求/任务均匀分配到多核心、多实例或多机器的策略与设施。 | 切蛋糕:每人一块,不让某人盘子堆满。 | Nginx ∕ Envoy、线程池 Work‑Stealing、K8s Service |
🏚️ | 背压 Back‑pressure | 当下游处理能力不足时,上游主动减速或阻塞,防止队列爆炸。 | “别再塞行李,我箱子快炸了!” | Reactive Streams、TCP 滑动窗口、Kafka Producer |
☠️ | 死锁 Deadlock | 多个线程因循环等待资源而永久阻塞的状态。 | A 抓着钥匙等 B,B 抓着钥匙等 A→谁也别想动。 | 双锁嵌套、数据库事务相互等待 |
3️⃣ 六大并发模型深度解析
阅读提示:每节都按「定义 → 优点 → 缺点 → 典型场景」展开。
3.1 🖥️ 进程模型
- 定义:内核级隔离,独立虚拟地址空间。
- 优点:崩溃互不影响;安全沙箱;能跨机。
- 缺点:创建/切换重;IPC 慢。
- 典型场景:微服务、浏览器多进程、容器化部署。
3.2 🧵 线程模型
- 定义:进程内共享内存的轻量分身,内核抢占。
- 优点:切换快;共享数据高效。
- 缺点:锁竞争、死锁、缓存失效。
- 典型场景:加密运算、图像渲染、并行压缩。
3.3 🌱 协程模型
- 定义:用户态调度的小任务,协作让出 CPU。
- 优点:百万并发;代码像同步。
- 缺点:阻塞系统调用会卡线程;利用多核需线程池。
- 典型场景:高并发 HTTP、WebSocket、爬虫。
3.4 🔄 事件驱动
- 定义:单线程
poll → 回调
循环,复用多路 I/O。 - 优点:I/O 吞吐高;无锁。
- 缺点:CPU 密集阻塞;回调地狱。
- 典型场景:Node.js SSR、浏览器主线程、Nginx。
3.5 ✉️ 消息传递
- 定义:任务间只靠消息队列沟通,不共享内存。
- 优点:避免竞态;易做分布式。
- 缺点:序列化 & 拷贝;背压调优。
- 典型场景:Go
chan
协作、Ruststd::sync::mpsc
、微服务异步总线。
3.6 🤖 Actor 模型
- 定义:
Actor = 状态 + 私有邮箱
,顺序收消息。 - 优点:监督树容错;天然对象隔离。
- 缺点:Actor 激增→调度开销;调试难。
- 典型场景:Erlang/Elixir 电信、Akka Cluster、IoT 中心。
4️⃣ 如何选型:决策思维导图
┌─ I/O 密集 ──> 协程 ▸ 事件驱动
需求类型 ─┤
├─ CPU 密集 ──> 线程 ▸ Actor
├─ 高隔离 ──> 进程 ▸ Actor
└─ 分布式 ──> Actor ▸ 消息传递
温馨提示:真实项目通常“混搭”以扬长避短——
线程吃多核,协程撑并发,Channel 解耦共享。
5️⃣ 典型组合与实战
场景 | 组合 | 亮点 |
---|---|---|
Rust Tokio Web | 多线程 Runtime + async/.await + mpsc | 并发抽象清晰,并行吃满核心 |
Go 微服务 | goroutine + Channel + Work‑Stealing | 百万连接;背压友好 |
Erlang/Elixir | Actor + 监督树 + 节点热升级 | “让进程随便崩”,秒级恢复 |
Node.js SSR | 事件循环 + Promise | 单线程无锁,高效 I/O |
🏁 结语:并发抽象、并行落地
- 并发:思考 怎么把活排好队。
- 并行:把排好队的活扔到 多核/多机同时干。
- 工程实践里,一定 组合模型 才能兼顾性能、可靠性与易用性。
- 深刻理解 关键概念(锁、消息、调度、背压……)是写出正确并发程序的第一步。
终极口诀:
“并发管队列,并行管核;概念先吃透,模型再拼合。”