让我们直接从代码开始入手:
import salabim as sim
class CustomerGenerator(sim.Component):
def process(self):
print(env.main().scheduled_time())
while 1:
Customer()
yield self.hold(sim.Uniform(5,15).sample())
class Customer(sim.Component):
def process(self):
self.enter(waitingline)
for clerk in clerks:
if clerk.ispassive():
clerk.activate()
yield self.passivate()
class Clerk(sim.Component):
def process(self):
while 1:
while len(waitingline) == 0:
yield self.passivate()
self.customer = waitingline.pop()
yield self.hold(30)
self.customer.activate()
env = sim.Environment(trace=True)
CustomerGenerator()
clerks = [Clerk() for _ in range(3)]
waitingline = sim.Queue('waitingline')
env.run(till=5000)
waitingline.print_histograms()
waitingline.print_info()
这是一个银行中事件处理的例子,我们假设在一个银行中,顾客出现时间的概率遵循[5,15](min)之间的均匀分布,顾客到来后需要排队办理事务,银行中共有三名柜员,一名柜员相应一位顾客的请求的时间为30分钟。让我们来分析一下整体的结构。
在这个例子中,系统中共有两个成员,分别为顾客和柜员。这两个角色分别会按照一定的逻辑来做事。
首先我们先看柜员部分:
class Clerk(sim.Component):
def process(self):
while 1:
while len(waitingline) == 0:
yield self.passivate()
self.customer = waitingline.pop()
yield self.hold(30)
self.customer.activate()
在定义柜员时,我们让其继承于sim.Component.
首先使用一个判断语句来对大厅的工作情况进行判断:
while len(waitingline) == 0:
yield self.passivate()
当队伍长度为0时,表示没有顾客需要服务,这时使用yield来对这名柜员的状态进行切换,使其休眠,并等待被唤醒。
如果队伍长度大于0则会跳过该判断,使用
self.customer = waitingline.pop()
yield self.hold(30)
self.customer.activate()
对队列中的第一个顾客进行服务,并将其从服务队列中提取出来。服务时间为30分钟,因此使用yield使柜员忙碌并持续30分钟。最后,使用activate来对该顾客进行唤醒。
如果看到这儿奇怪为什么顾客需要被唤醒,那么接下来来看顾客部分的逻辑:
class Customer(sim.Component):
def process(self):
self.enter(waitingline)
for clerk in clerks:
if clerk.ispassive():
clerk.activate()
yield self.passivate()
首先,Customer也继承于sim.Component.
我们可以看到,在def process函数下,Customer相较于Clerk少了一个while 1的循环,这是因为对于顾客来说被服务是一次性的过程,当顾客被服务完成后顾客将离开该系统。
首先
self.enter(waitingline)
使用该行代码让顾客进入等待队列中,等待队列在后面后定义。
然后在现有的clerk中寻找空闲的柜员,上文中提到,如果柜员判定排队队伍长度为零时柜员会进入休眠状态,此时如果有顾客来到则应该对其进行唤醒。唤醒柜员后使用yield函数进入休眠,这与之前柜员的代码相呼应,在经过了30min的服务后,柜员会将其重新唤醒。
看完了系统中两个主体的逻辑后,我们来看看其他的部分的定义,首先是系统环境的加载:
env = sim.Environment(trace=True)
这个函数的具体用法会在之后提到,在现在这个阶段是需要将其加入到代码中初始化和定义我们的模拟系统即可。这里的trace选择ture的话会在运行完后在命令行生成运行日志以查看模拟的每一步的系统情况。
然后等待队列的定义:
waitingline = sim.Queue('waitingline')
可以看到这也是salabim库中自带的定义方式。
那么在系统中的两个主体该如何添加呢,这里是需要注意的点,首先我们先思考柜员。
柜员相对于系统来说是固定的,他们的数量从开始到结束是不变的,因此我们可以简单的使用
clerks = [Clerk() for _ in range(3)]
来对系统中添加三个柜员。
而顾客是一个变量,他们会随机到来并在结束后离开系统,离开系统并不需要我们来进行定义,因为一旦其过程运行完毕后系统会自动执行,而顾客的到来需要我们来进行定义。
因此在这里我们设计了一个生成器,把它也作为系统中的一个主体,这个东西不是很好理解,如果有小伙伴玩过mc可以将它想象成一个刷怪笼,会随机的生成一些怪物,只不过在此将怪物替换称为顾客。
class CustomerGenerator(sim.Component):
def process(self):
print(env.main().scheduled_time())
while 1:
Customer()
yield self.hold(sim.Uniform(5,15).sample())
其运行原理十分简单,在循环中不断的生成Customer类的主体,并且运用yield功能陷入随机的休眠时间,这个时间遵守[5,15]的均匀分布。
因此我们只需要往系统中添加这个生成器就可以不断的生成顾客了:
CustomerGenerator()
最后,我们希望可以模拟5000分钟的系统情况,因此我们定义系统一直运行到5000分钟:
env.run(till=5000)
最后让我们来看看运行结果,这里截取了一部分以做演示:
24+ 1563.935 clerk.1 current
25 customer.151 activate scheduled for 1563.935 @ 16+
23 customer.154 leave waitingline
24 clerk.1 hold +30.000 scheduled for 1593.935 @ 24+
左边部分表示这是第几步,中间部分可以看到各个主题的情况,customer.151中的151表示这时第151个客户,可以看到这一步中151号顾客被激活,说明其刚完成服务,customer154正在等待,而柜员1正在为前一个顾客服务,通过这个对话可以很好的追踪系统的运行情况。
在下一个章节我们会更深入的讨论这个库的其他功能。