并发编程模式:从生产者-消费者到 Actor 的工程实践

一、并发与并行:先搞清楚我们在解决什么
很多人容易把并发(Concurrency)和并行(Parallelism)混为一谈。简单来说,并发是"同时处理多个任务"的结构设计,而并行是"同时执行多个任务"的物理实现。单核 CPU 可以通过时间片轮转实现并发,但无法实现并行。
并发模式的核心问题不是"如何让多个任务同时跑",而是"如何组织多个任务之间的协作关系"。
选哪种模式,主要看任务之间的数据依赖、通信频率和错误隔离需求。生产者-消费者适合数据流水线,Actor 适合高隔离的独立任务,CSP 适合同步通信的协程协作。选错模式,代码复杂度会直线上升,甚至引入难以排查的死锁和竞态条件。
二、三种主流模式的本质差异
这三种模式在通信方式、隔离级别和错误传播机制上有着本质区别。
生产者-消费者模式是最基础的并发模型。生产者往有界缓冲区里塞数据,消费者从里面取数据。有界缓冲区把两者解耦了:生产者不用等消费者处理完,消费者也不用等生产者产数据。缓冲区满了,生产者会被阻塞(背压机制),防止数据积压撑爆内存。
Actor 模式把并发单元封装成独立的 Actor,每个 Actor 有自己的邮箱(Mailbox)和私有状态。Actor 之间只通过异步消息通信,不共享状态。这种"无共享"设计直接消除了数据竞争,因为每个 Actor 的状态只能被自己访问。错误隔离性也很好------一个 Actor 崩了,不会直接带崩其他 Actor,毕竟它们之间没有共享内存。
CSP(Communicating Sequential Processes)模式用同步通道(Channel)连接协程。这和 Actor 的异步消息不同,CSP 的通道是同步的:发送方和接收方必须在通道上同时就绪,数据传输才会发生。这种同步语义让 CSP 天然支持背压------接收方处理不过来,发送方自动阻塞,不需要额外的缓冲区管理。
三、生产级代码实现
下面用 Python 实现了三种模式,包含了错误处理和背压机制。
python
import asyncio
import time
from dataclasses import dataclass, field
from typing import Any, Optional, Callable, Awaitable
from enum import Enum
# ============================================================
# 模式1:生产者-消费者(带背压的有界队列)
# ============================================================
async def producer_consumer_pattern():
"""
生产者-消费者模式:使用 asyncio.Queue 实现有界缓冲区
队列满时 put() 自动阻塞,实现背压
"""
queue: asyncio.Queue[str] = asyncio.Queue(maxsize=10)
async def producer(producer_id: int, count: int) -> None:
for i in range(count):
item = f"P{producer_id}-Item{i}"
# 队列满时自动阻塞,背压机制防止内存溢出
await queue.put(item)
await asyncio.sleep(0.01) # 模拟生产延迟
# 发送结束信号
await queue.put(None)
async def consumer(consumer_id: int) -> int:
processed = 0
while True:
item = await queue.get()
if item is None:
# 结束信号:重新放入队列供其他消费者使用
await queue.put(None)
break
# 模拟消费处理
await asyncio.sleep(0.02)
processed += 1
queue.task_done()
return processed
# 启动2个生产者和3个消费者
producers = [asyncio.create_task(producer(i, 20)) for i in range(2)]
consumers = [asyncio.create_task(consumer(i)) for i in range(3)]
await asyncio.gather(*producers)
results = await asyncio.gather(*consumers)
print(f"消费者处理量: {results}")
# ============================================================
# 模式2:Actor 模式(异步消息 + 邮箱隔离)
# ============================================================
@dataclass
class ActorMessage:
"""Actor 消息:包含发送者、动作和负载"""
sender: str
action: str
payload: Any = None
class Actor:
"""
Actor 基类:每个 Actor 有独立的邮箱和状态
Actor 之间只能通过消息通信,不共享状态
"""
def __init__(self, actor_id: str):
self.actor_id = actor_id
self.mailbox: asyncio.Queue[ActorMessage] = asyncio.Queue()
self._running = False
self._state: dict[str, Any] = {} # Actor 私有状态,外部不可直接访问
async def send(self, target: 'Actor', action: str,
payload: Any = None) -> None:
"""向目标 Actor 发送异步消息"""
msg = ActorMessage(
sender=self.actor_id,
action=action,
payload=payload,
)
await target.mailbox.put(msg)
async def receive(self) -> ActorMessage:
"""从邮箱接收消息:邮箱为空时阻塞等待"""
return await self.mailbox.get()
async def handle(self, message: ActorMessage) -> Optional[Any]:
"""消息处理器:子类重写此方法实现具体逻辑"""
if message.action == "query_state":
return dict(self._state)
elif message.action == "update_state":
key, value = message.payload
self._state[key] = value
return "ok"
return None
async def run(self) -> None:
"""Actor 主循环:持续从邮箱接收并处理消息"""
self._running = True
while self._running:
try:
msg = await asyncio.wait_for(self.receive(), timeout=1.0)
await self.handle(msg)
except asyncio.TimeoutError:
continue # 超时后继续等待,允许检查 _running 标志
def stop(self) -> None:
"""停止 Actor 主循环"""
self._running = False
async def actor_pattern():
"""Actor 模式示例:两个 Actor 通过消息协作"""
actor_a = Actor("worker-a")
actor_b = Actor("worker-b")
# 启动 Actor 主循环
task_a = asyncio.create_task(actor_a.run())
task_b = asyncio.create_task(actor_b.run())
# Actor A 向 Actor B 发送消息
await actor_a.send(actor_b, "update_state", ("status", "processing"))
await actor_a.send(actor_b, "query_state", None)
# 停止 Actor
actor_a.stop()
actor_b.stop()
await asyncio.gather(task_a, task_b)
# ============================================================
# 模式3:CSP 模式(同步通道 + 协程协作)
# ============================================================
class Channel:
"""
同步通道:发送方和接收方必须同时就绪
与 asyncio.Queue 不同,Channel 没有缓冲区
天然支持背压:接收方未就绪时发送方阻塞
"""
def __init__(self):
self._sender_waiter: Optional[asyncio.Future] = None
self._receiver_waiter: Optional[asyncio.Future] = None
self._value: Any = None
async def send(self, value: Any) -> None:
"""发送值:等待接收方就绪后完成传输"""
if self._receiver_waiter is not None:
# 接收方已在等待,直接完成传输
self._value = value
receiver = self._receiver_waiter
self._receiver_waiter = None
receiver.set_result(None)
else:
# 等待接收方就绪
self._sender_waiter = asyncio.get_event_loop().create_future()
self._value = value
await self._sender_waiter
async def receive(self) -> Any:
"""接收值:等待发送方就绪后完成传输"""
if self._sender_waiter is not None:
# 发送方已在等待,直接完成传输
sender = self._sender_waiter
self._sender_waiter = None
sender.set_result(None)
return self._value
else:
# 等待发送方就绪
self._receiver_waiter = asyncio.get_event_loop().create_future()
await self._receiver_waiter
return self._value
async def csp_pattern():
"""CSP 模式示例:两个协程通过同步通道协作"""
ch1 = Channel()
ch2 = Channel()
async def process_stage1():
"""第一阶段:接收原始数据,处理后发送到下一阶段"""
for i in range(5):
raw = f"data-{i}"
processed = raw.upper()
await ch1.send(processed) # 同步发送:等待第二阶段就绪
async def process_stage2():
"""第二阶段:接收第一阶段结果,进一步处理"""
for _ in range(5):
data = await ch1.receive() # 同步接收:等待第一阶段发送
result = f"[{data}]"
await ch2.send(result)
async def collector():
"""收集最终结果"""
for _ in range(5):
result = await ch2.receive()
print(f"最终结果: {result}")
# 三个协程通过通道同步协作
await asyncio.gather(
process_stage1(),
process_stage2(),
collector(),
)
if __name__ == "__main__":
print("=== 生产者-消费者模式 ===")
asyncio.run(producer_consumer_pattern())
print("\n=== Actor 模式 ===")
asyncio.run(actor_pattern())
print("\n=== CSP 模式 ===")
asyncio.run(csp_pattern())
从代码结构就能看出差异:生产者-消费者靠有界队列解耦,Actor 靠邮箱和消息,CSP 靠同步通道。选哪种,看任务间的耦合程度和错误隔离需求。
四、实战中的坑与选型建议
生产者-消费者的队列积压:如果生产速度持续大于消费速度,有界队列的背压会阻塞生产者,但无界队列会导致内存溢出。必须根据业务 SLA 计算合理的队列大小,并在队列使用率超过阈值时触发告警。
Actor 模式的邮箱溢出:Actor 的邮箱如果无限增长,同样会耗尽内存。解决方案是为邮箱设置容量上限,超出时采用丢弃策略(如丢弃最旧的消息)或拒绝策略(向发送方返回错误)。
CSP 模式的死锁风险:同步通道要求发送方和接收方同时就绪。如果两个协程互相等待对方先发送,就会产生死锁。避免死锁的关键是确保通道的使用方向是单向的,不要形成循环依赖。
适用边界:
- 生产者-消费者:适合数据流水线场景(日志处理、ETL)
- Actor:适合需要强隔离的独立任务(聊天室、游戏服务器)
- CSP:适合需要精确同步的协程协作(算法流水线、数据处理管道)
五、总结
并发模式的选择要从任务间的通信频率、隔离需求和错误传播要求出发。
- 生产者-消费者最简单通用,适合大多数数据流水线场景
- Actor 的无共享设计消除了数据竞争,适合高隔离需求
- CSP 的同步通道天然支持背压,适合需要精确同步的协作场景
落地建议:从生产者-消费者模式起步,满足 80% 的场景;在需要强隔离时引入 Actor 模式;在需要精确同步时使用 CSP 通道。无论选择哪种模式,都必须处理背压和错误传播,否则并发系统在生产环境中不可靠。