仿真程序里最难处理的,往往不是单个进程的逻辑,而是进程之间怎么说话 。一个生产者在疯狂输出消息,消费者却在做自己的事------它们步调不一,互不等待,却又必须在某个时刻完成交接。SimPy 给出的答案,是用 Store 充当"管道",让消息在里面排队等候,谁准备好了谁来取。
管道的本质:一个会等人的队列
simpy.Store 是整套机制的基石。你可以把它想象成一条传送带------生产者把东西放上去,消费者从另一端拿走。如果传送带是空的,消费者就在那儿等着,直到有东西出现。
操作上极其简洁:
| 操作 | 方法 | 实际行为 |
|---|---|---|
| 放入消息 | store.put(item) |
压入队列,立即返回 |
| 取出消息 | yield store.get() |
队列空时挂起等待 |
| 设置容量 | simpy.Store(env, capacity=N) |
限制缓冲区大小,默认无限 |
那句 yield store.get() 是整个机制的灵魂------消费者在这里"睡着",直到管道里有消息,才被唤醒继续跑。
三种说话方式
一对一:最朴素的传话
一个生产者,一根管道,一个消费者。代码写起来几乎不需要思考:
python
pipe = simpy.Store(env)
env.process(message_generator('Generator A', env, pipe))
env.process(message_consumer('Consumer A', env, pipe))
生产者往管道里塞消息,消费者从管道里取。谁快谁慢都无所谓,Store 帮你兜着。
多对一:多个声音,一个耳朵
多个生产者共用同一个 Store,消费者照单全收。这个场景 SimPy 原生就支持,不需要任何额外封装------多个进程同时往同一个 Store 里 put(),消费者依次处理,天然有序。
一对多:广播,才是真正的难题
这里才是真正需要动脑子的地方。
原生的 Store 有个天然局限:一条消息只能被一个消费者取走 。如果你想让同一条消息同时送达多个接收者,就得自己造工具。官方文档给出的方案叫 BroadcastPipe,思路直白而聪明:
与其让多个消费者抢同一个
Store,不如给每个消费者单独开一条管道,消息进来时,往所有管道里各塞一份。
python
class BroadcastPipe:
def __init__(self, env, capacity=simpy.core.Infinity):
self.env = env
self.capacity = capacity
self.pipes = [] # 每个消费者对应一个 Store
def put(self, value):
if not self.pipes:
raise RuntimeError('There are no output pipes.')
events = [store.put(value) for store in self.pipes]
return self.env.all_of(events) # 等所有人都收到,才算发送完成
def get_output_conn(self):
pipe = simpy.Store(self.env, capacity=self.capacity)
self.pipes.append(pipe)
return pipe
每个消费者调用 get_output_conn() 领一根专属管道,生产者发消息时,BroadcastPipe 负责把消息复制并分发给所有人。env.all_of(events) 则确保这一轮广播真正完成------所有管道都收到了,才继续。
用起来也相当清爽:
python
bc_pipe = BroadcastPipe(env)
env.process(message_generator('Generator A', env, bc_pipe))
env.process(message_consumer('Consumer A', env, bc_pipe.get_output_conn()))
env.process(message_consumer('Consumer B', env, bc_pipe.get_output_conn()))
那条"迟到"的消息
文档里藏着一个细节,值得单独拿出来说。每条消息被打上了发送时的时间戳:
python
msg = (env.now, f'{name} says hello at {env.now}')
消费者取到消息后,会把时间戳和当前时间做个比较:
python
if msg[0] < env.now:
print(f'LATE Getting Message: ...')
else:
print(f'at time {env.now}: ...')
逻辑很简单:如果消息发出去的时候是 t=65,消费者却到 t=66 才来取,那这条消息就"迟到"了。原因通常是消费者在忙别的事------比如 yield env.timeout(random.randint(4, 8)),处理上一条消息花了太长时间,导致新消息在管道里压了一会儿才被领走。
这个机制在实际建模中很有用。它能告诉你系统在什么时间点开始"跟不上",哪个环节成了瓶颈。
消息流向一览

这套机制能用在哪儿
说到底,Store + BroadcastPipe 这套组合,就是在仿真世界里实现了一个轻量级的发布-订阅模式。凡是涉及"一方产生、多方消费"的场景,它都能派上用场:
- 生产线:机器产出零件,多个工位异步领取
- 网络仿真:服务器广播数据包,多个客户端各自接收
- 事件驱动系统:传感器触发事件,多个处理模块订阅响应
- 消息队列建模:模拟 Kafka、RabbitMQ 这类中间件的行为
SimPy 的进程通信没有魔法,核心就是用资源做缓冲,用事件做同步。一旦理解了这个思路,再复杂的进程拓扑,拆开来看都是这几块积木的组合。