信号从不瞬间到达
现实世界里,没有什么传输是瞬间完成的。
电信号在铜缆里跑,无线电波在空气里穿,数据包在网络里转------它们都需要时间。哪怕快如光速,从地球发一条信号到月球,单程也要走一秒多。这段"在路上"的时间,就是延迟。
在仿真建模里,处理这种延迟有一种很优雅的思路:不要让发送方去操心,也不要让接收方去等待,把延迟这件事,单独交给"传输介质"来负责。 这就是 SimPy 中 Event Latency 模式的核心。
一根会"拖时间"的电缆
示例里构造了一个 Cable 类,用来模拟一根有传播延迟的电缆。它的内部结构其实很简单:一个延迟时长 delay,加上一个 SimPy 的 Store(可以理解为消息到达后的收件箱)。
python
class Cable:
def __init__(self, env, delay):
self.env = env
self.delay = delay
self.store = simpy.Store(env)
def latency(self, value):
yield self.env.timeout(self.delay)
self.store.put(value)
def put(self, value):
self.env.process(self.latency(value))
def get(self):
return self.store.get()
当发送方调用 cable.put() 时,电缆并不会立刻把消息塞进收件箱------它会悄悄启动一个独立的 latency 进程,让这条消息在"传输途中"老老实实等够 delay 个时间单位,然后才落地进入 Store。
接收方调用 cable.get() 时,如果收件箱还是空的,它就只能阻塞在那里,直到消息真正到达为止。
这个设计的妙处在于:发送方不知道延迟,接收方感知不到延迟,延迟被完整地藏在了电缆里。
发送方和接收方,各干各的
发送方的逻辑干净得像一张白纸:
python
def sender(env, cable):
while True:
yield env.timeout(5)
cable.put(f'Sender sent this at {env.now}')
每隔 5 个时间单位,发一条消息,仅此而已。它不需要知道对面什么时候能收到,也不需要等对面确认。
接收方同样简单:
python
def receiver(env, cable):
while True:
msg = yield cable.get()
print(f'Received this at {env.now} while {msg}')
坐在那里等,消息来了就打印,然后继续等。延迟的存在对它来说完全透明。
时间线上发生了什么
把仿真跑起来,输出是这样的:
kotlin
Received this at 15 while Sender sent this at 5
Received this at 20 while Sender sent this at 10
Received this at 25 while Sender sent this at 15
...
用时间轴拆开来看,逻辑一目了然:
ini
t=5 发送方发出第 1 条消息,Cable 启动延迟进程(倒计时 10)
t=10 发送方发出第 2 条消息,Cable 再启动一个延迟进程(倒计时 10)
t=15 第 1 条消息延迟到期,落入 Store → 接收方收到,打印
t=20 第 2 条消息延迟到期,落入 Store → 接收方收到,打印
...
规律很清晰:每条消息的接收时间,恰好等于发送时间加上延迟,即
t接收=t发送+Δt延迟
每条消息都有自己独立的延迟进程,互不干扰,多条消息可以同时"在路上",顺序也自然保持先进先出。
为什么要这样设计
这套模式背后有一个朴素的工程直觉:让每个部分只做自己该做的事。
scss
[ 发送方 ] ──put()──▶ [ Cable · 延迟层 ] ──store.put()──▶ [ 接收方 ]
只管发 只管传播时延 只管收
三层彻底解耦。哪天想把延迟从 10 改成 20,或者换成一个随机延迟模型,只需要动 Cable 这一处,发送方和接收方的代码一行都不用碰。
这种思路在现实建模中用途很广:
- 模拟光纤或铜缆的信号传播
- 模拟无线电、卫星通信的传输时延
- 模拟网络数据包在路由器间的转发延迟
- 模拟工厂流水线上工序之间的搬运时间
- 构建解耦的消息队列,让生产者和消费者各自独立运转
把延迟还给介质本身
Event Latency 的本质,是一种归责清晰的建模哲学。
延迟是电缆的属性,不是发送方的负担,也不是接收方的问题。把它封装进去,让电缆自己去"拖时间",整个模型的结构就会变得异常清爽。
simpy.Store 在这里扮演的是"到达缓冲区"的角色,只有延迟进程跑完,消息才会落进去,接收方的等待才会被唤醒。这一个小小的设计,把时序关系处理得滴水不漏。
下次在仿真里遇到需要建模传播延迟的场景,不妨想想这根"会拖时间的电缆"------它比你想象的好用得多。