使用协程做离散事件仿真。
前面对yield from的理解花了好长时间取理解,好几天想着想着天就亮了,想到后面感觉脑子都快炸了,现在总算稍微有点明白了。
新开一篇随笔,记录我对书中离散事件仿真的理解,按照说中的说法,如果这篇理解好了,能够让让我更好的理解asyncio,Twisted,Tornado等库如何在但线程中管理多个并发活动。
感觉书本学习就是找出书中的错误。
先给自己两个巴掌,基础知道出现了一个大漏洞:
sorted函数:
In [2]: sorted? Signature: sorted(iterable, /, *, key=None, reverse=False) Docstring: Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order. Type: builtin_function_or_method
我一直认为sorted返回的是一个全新的对象列表,是全新的,但是确实浅拷贝,在这次测试中,发现了一个问题,还好及时学习了。
In [41]: l = ([1,1],[2,2]) In [42]: l1 = sorted(l) In [43]: l[0] is l1[0] Out[43]: True
In [47]: l == l1 Out[47]: False In [48]: l Out[48]: ([1, 1], [2, 2]) In [49]: l1 Out[49]: [[1, 1], [2, 2]] In [50]: l == l1 Out[50]: False In [51]: l is l1 Out[51]: False
其实sorted在运行的时候,应该首相对可迭代对象进行了list,但list其实是对该对象列表话操作以后,里面的元素都是浅拷贝。
In [52]: l Out[52]: ([1, 1], [2, 2]) In [53]: l2 = list(l) In [54]: l2 Out[54]: [[1, 1], [2, 2]] In [55]: l2[0] is l[0] Out[55]: True
基础没学好,重新温习一下吧。
下面进入书中内容。
书中主要模拟了一个出租车公司的运行情况。
import random
import collections
import queue
import argparse
import time
import inspect
# 定义一些常量与默认参数
DEFAULT_NUMBER_OF_TAXIS = 3 # 默认出租车数量
DEFAULT_END_TIME = 180 # 默认一次出车事件单位应该是(minute)
SEARCH_DURATION = 5
TRIP_DURATION = 20
DEPARTURE_INTERVAL = 5
# 三个参数分别为事件,车辆编号,发现的情况
Event = collections.namedtuple('Event','time proc action')
# 一个车辆的轨迹
def taxi_process(ident, trips, start_time=0):
"""每次状态变化时向仿真程序产出一个事件
indent为车辆编号,trips为做几单生意
"""
time = yield Event(start_time, ident, 'leave garage') # 从车库出发
# 开始做生意了
for i in range(trips):
time = yield Event(time, ident, 'pick up passenger')
time = yield Event(time, ident, 'drop off passenger')
# 打包回家了
yield Event(time, ident, 'going home')
# BEGIN TAXI_SIMULATOR
class Simulator:
def __init__(self, proc_map):
self.events = queue.PriorityQueue() # 事件优先队列
self.procs = dict(proc_map) # 创建出租车对的副本
def run(self, end_time):
'''调度并显示事件,知道事件结束'''
# 调度各辆出租车的第一个事件
for _, proc in sorted(self.procs.items()): # 对出租车副本进行排序,按照k默认key进行过排序
# 上面这个sorted我觉的是多余的,我经过了测试放和不放都一样。
# 本来放入的队列就是优先队列,没必要对各个车辆协程根据key就是车辆编号进行排序
first_event = next(proc) # 预激每个车辆协程,取出每个车辆的初始event
self.events.put(first_event) # 优先队列放入各个enent,默认按照time大小放入。
# 此次仿真的主循环
sim_time = 0 # 初始化开始时间
while sim_time < end_time: # 监测时间是否超时。
'''从这个循环一取一方可以看出来,如果没有车辆回家,事件队列里面的消息数量,一致就是
车队车辆的数量,每一辆车必须把最新的情况,放入事件队列里面。
'''
if self.events.empty():
print('*** end of events ***') # 事件队列没有消息了,说明没有车辆在路上了。
break # 退出
current_event = self.events.get() # 按照event第一元素的大小,从第一元素时间取出事件。
sim_time, proc_id, previous_action = current_event # 把事件元祖拆包取值
print('taxi:', proc_id * ' ', current_event) # 屏幕显示显示该事件车辆的状况
active_proc = self.procs[proc_id] # 取出该事件发生的时候的车辆
next_time = sim_time + computer_duration(previous_action) # 根据算法算出下次时间
try:
next_event = active_proc.send(next_time) # 发送给车辆协议下次运行的时间,获取返回的事件
except StopIteration: # 如果没有yield产出,接受到StopIteration,说明该车已经计划出车任务完成。
del self.procs[proc_id] # 从车队副本里面删除这辆车
else:
self.events.put(next_event) # 把事件放入事件容器里面。
else:
msg = '*** end of simulation time: {} events pending ***' # 当while循环正常正常,说明sim_time>end_time
print(msg.format(self.events.qsize())) # 上报没有完成的任务车辆。
def computer_duration(previous_action):
'''使用指数分布计算操作的耗时'''
if previous_action in ['leave garage', 'drop off passenger']: # 当离开车库,或者放下乘客
interval = SEARCH_DURATION # 花费的时间给个参数
elif previous_action == 'pick up passenger': # 当接上乘客给出时间
interval = TRIP_DURATION
elif previous_action == 'going home': # 当回家时,给的时间,其实这个时间已经无用了。
interval = 1 # 因为发送去这个时间就删除了这个车子了,没有返回信息了
else:
raise ValueError('Unknown previous_action: %s ' % previous_action) # 报错
return int(random.expovariate(1/interval) +1) # 这个一个非常有意思的随机生成符合指数分布的随机数
def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, seed=None):
if seed is not None: # 设置随机数的规则定位
random.seed(seed)
# taxi_process参数第一个车辆编号, 第二个做几单生意,第三个初始开始时间
taxis = {i: taxi_process(i, (i+1)*2, i*DEPARTURE_INTERVAL) for i in range(num_taxis)}
# 新建一个车队,感觉这个车队,这样的安排其实不合理,后面的车子解的单太多了
sim = Simulator(taxis)
# 车子放去模拟器中
sim.run(end_time)
# 车子跑起来,设定结束时间。
if __name__ == '__main__':
# argparse是一个非常强大的对脚本输入参数的解释与筛选工具
parser = argparse.ArgumentParser(
description='Taxi fleet simulator')
# -e短名 --end_time长名 接收参数为int,默认参数为DEFAULT_END_TIME,还有help信息
parser.add_argument('-e', '--end_time', type=int,
default=DEFAULT_END_TIME,
help='simulation end time; default = %s'
% DEFAULT_END_TIME)
parser.add_argument('-t', '--taxis', type=int,
default=DEFAULT_NUMBER_OF_TAXIS,
help=f'number of taxis running; default '
f'= {DEFAULT_NUMBER_OF_TAXIS}')
parser.add_argument('-s', '--seed', type=int, default=None,
help='random generator seed (for testing)')
args = parser.parse_args() # 解析参数
# 通过属性(长名)获取具体参数
main(args.end_time, args.taxis, args.seed)
shijianzhongdeMacBook-Pro:第十六章 shijianzhong$ python3 taxi_sim.py -h
usage: taxi_sim.py [-h] [-e END_TIME] [-t TAXIS] [-s SEED]
Taxi fleet simulator
optional arguments:
-h, --help show this help message and exit
-e END_TIME, --end_time END_TIME
simulation end time; default = 180
-t TAXIS, --taxis TAXIS
number of taxis running; default = 3
-s SEED, --seed SEED random generator seed (for testing)
shijianzhongdeMacBook-Pro:第十六章 shijianzhong$ python3 taxi_sim.py -t 3 -e 200
taxi: Event(time=0, proc=0, action='leave garage')
taxi: Event(time=1, proc=0, action='pick up passenger')
taxi: Event(time=5, proc=1, action='leave garage')
taxi: Event(time=10, proc=2, action='leave garage')
taxi: Event(time=11, proc=0, action='drop off passenger')
taxi: Event(time=11, proc=1, action='pick up passenger')
taxi: Event(time=18, proc=0, action='pick up passenger')
taxi: Event(time=23, proc=2, action='pick up passenger')
taxi: Event(time=41, proc=0, action='drop off passenger')
taxi: Event(time=42, proc=2, action='drop off passenger')
taxi: Event(time=43, proc=0, action='going home')
taxi: Event(time=54, proc=2, action='pick up passenger')
taxi: Event(time=64, proc=2, action='drop off passenger')
taxi: Event(time=69, proc=1, action='drop off passenger')
taxi: Event(time=70, proc=1, action='pick up passenger')
taxi: Event(time=73, proc=2, action='pick up passenger')
taxi: Event(time=77, proc=2, action='drop off passenger')
taxi: Event(time=82, proc=1, action='drop off passenger')
taxi: Event(time=86, proc=1, action='pick up passenger')
taxi: Event(time=97, proc=2, action='pick up passenger')
taxi: Event(time=102, proc=1, action='drop off passenger')
taxi: Event(time=109, proc=1, action='pick up passenger')
taxi: Event(time=112, proc=1, action='drop off passenger')
taxi: Event(time=119, proc=2, action='drop off passenger')
taxi: Event(time=120, proc=1, action='going home')
taxi: Event(time=128, proc=2, action='pick up passenger')
taxi: Event(time=145, proc=2, action='drop off passenger')
taxi: Event(time=155, proc=2, action='pick up passenger')
taxi: Event(time=162, proc=2, action='drop off passenger')
taxi: Event(time=164, proc=2, action='going home')
*** end of events ***
shijianzhongdeMacBook-Pro:第十六章 shijianzhong$
简单的来插入说明一下这个random.expovariate的指数分布,有意思的。
In [83]: l = [random.expovariate(1/20) for i in range(10000)] In [84]: sum(l)/len(l) Out[84]: 19.71606700143721 In [85]: len([i for i in l if i >=20]) Out[85]: 3654 In [86]: l = [random.expovariate(1/20) for i in range(10000)] In [87]: sum(l)/len(l) Out[87]: 20.075195351365277 In [88]: len([i for i in l if i >=20]) Out[88]: 3666 In [89]: l = [random.expovariate(1/20) for i in range(10000)] In [90]: sum(l)/len(l) Out[90]: 20.05689060176687 In [91]: len([i for i in l if i >=20]) Out[91]: 3682
最后真的很感概这个逻辑的厉害,我觉的这里比较厉害的是第一用到了优先队列,避免了很多自己写逻辑的麻烦,每次出来的事件都是最靠前的时间。
第二个是模型的设计,每个车辆是个协程,每个事件都是yield生成的。
很厉害的是把事件放入优先队列里面,事件里面有一个车辆ID,等取出最新生成的事件以后,拿出取出对应的车队里面的车辆协程,生成下一次的事件放入优先队列。
一直到这个车子一天的工作任务结束。
这个优先队列就好比一个事件循环,worker一直从队列里面取出事件,分析后,拿到对应的协程,根据需求完成具体事件,完成后,把状态信息(事件)扔回队列,取下一个信息。
这个优先队列里面的信息都是代办事情,主进程只要守着那个队列,取出最紧要的任务的进行处理,每个出租车协程根据在优先队列里面保存的最新状态(事件),worked会对优先事件对应的每个出租车携程进行操作。
整个思路下来,我觉的这个模型的设置最牛逼,第二个是用了内置的优先队列。厉害的我的神,什么时候我能这么厉害就好了。
浙公网安备 33010602011771号