1 进程交互
离散事件仿真只有通过进程之间的相互作用才能引起人们的兴趣。
所以章节指南是关于:
(1)休眠直到唤醒(被动/重新激活)
(2)等待另一个进程终止
(3)中断另一个进程
前两项已经在事件指南中介绍过了,但是为了完整起见,我们还将在这里介绍它们。
进程交互的另一种可能场景是资源的使用,将在单独的指南中讨论。
2 休眠直到唤醒
想象一下您给一辆带有智能充电控制器的电动汽车建模。当车辆行驶时,控制器是被动的;在车辆连接到电网后重新激活,以便为蓄电池充电。
在SimPy 2中,这种模式被称为被动/重新激活。在SimPy 3中,您可以通过一个简单的共享事件来实现这一点:
from random import seed, randint
seed(23)
import simpy
class EV:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
self.bat_ctrl_proc = env.process(self.bat_ctrl(env))
self.bat_ctrl_reactivate = env.event()
def drive(self, env):
while True:
# 行驶20-40分钟
yield env.timeout(randint(20, 40))
# 停车1–6小时
print('开始停车的时间:', env.now)
self.bat_ctrl_reactivate.succeed() # "激活"
self.bat_ctrl_reactivate = env.event()
yield env.timeout(randint(60, 360))
print('结束停车时间:', env.now)
def bat_ctrl(self, env):
while True:
print('控制器被动模式时间:', env.now)
yield self.bat_ctrl_reactivate # "passivate"
print('控制器激活时间:', env.now)
# 智能充电 …
yield env.timeout(randint(30, 90))
env = simpy.Environment()
ev = EV(env)
env.run(until=150)
由于bat_ctrl()只等待一个常规事件,因此我们在SimPy 3中不再调用被动/重新激活。
3 等待另一个进程终止
上面的示例有一个问题:车辆可能希望停车的时间比充电电池所需的时间短(如果充电和停车都需要60到90分钟,则会出现这种情况)。
要解决这个问题,我们必须稍微调整我们的模型。每次电动汽车开始停车时,都会启动一个新的bat_ctrl()。然后,电动汽车等待停车时间结束和充电停止:
from random import seed, randint
import simpy
seed(23)
class EV:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
def drive(self, env):
while True:
# 行驶20-40分钟
yield env.timeout(randint(20, 40))
# 停车1–6小时
print('开始停车时间', env.now)
charging = env.process(self.bat_ctrl(env))
parking = env.timeout(randint(60, 360))
yield charging & parking
print('结束停车时间:', env.now)
def bat_ctrl(self, env):
print('控制器启动时间:', env.now)
# 智能充电 …
yield env.timeout(randint(30, 90))
print('控制器完成时间:', env.now)
env = simpy.Environment()
ev = EV(env)
env.run(until=310)
开始停车时间 29 控制器启动时间: 29 控制器完成时间: 60 结束停车时间: 131 开始停车时间 169 控制器启动时间: 169 控制器完成时间: 226
同样,没有什么新的和特别的事情发生(如果你读过Events指南)。SimPy进程也是事件,因此您可以生成它们并等待它们被触发。您还可以通过将两个事件用&连接(请参见一次等待多个事件),同时等待两个事件。
4 中断另一个进程
像往常一样,我们现在还有另一个问题:想象一下,一次旅行是非常紧急的,但是在目前的实施中,我们总是需要等到电池充满电。如果我们能打断…
幸运的是,确实有办法做到这一点。可以对进程调用interrupt()。这将在该进程中引发中断异常,并立即恢复:
from random import seed, randint
import simpy
seed(23)
class EV:
def __init__(self, env):
self.env = env
self.drive_proc = env.process(self.drive(env))
def drive(self, env):
while True:
# 行驶20-40分钟
yield env.timeout(randint(20, 40))
# 停车1小时
print('开始停车时间', env.now)
charging = env.process(self.bat_ctrl(env))
parking = env.timeout(60)
yield charging | parking
if not charging.triggered:
# 中断充电
charging.interrupt('该出发了!')
print('结束停车时间:', env.now)
def bat_ctrl(self, env):
print('充电器启动时间:', env.now)
try:
yield env.timeout(randint(60, 90))
print('充电器完成时间:', env.now)
except simpy.Interrupt as i:
# 充电被中断
print('充电器中断时间:', env.now, 'msg:', i.cause)
env = simpy.Environment()
ev = EV(env)
env.run(until=100)
开始停车时间 29 充电器启动时间: 29 结束停车时间: 89 充电器中断时间: 89 msg: 该出发了!
process.interrupt()实际上做的是调度一个中断事件以便立即执行。如果执行此事件,它将从当前正在等待的事件的回调中删除受影响进程的_resume()方法(请参阅target)。然后它将把中断异常抛出到进程中。
由于我们没有对进程的原始目标事件做任何特殊的操作,中断的进程在捕捉到中断后可以再次产生相同的事件——想象有人在等待商店开门,这个人可能会被电话打断。打完电话后,他(她)会检查商店是否已经开门,是否进入或继续等待。