使用协程做离散事件仿真。

前面对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会对优先事件对应的每个出租车携程进行操作。

 

整个思路下来,我觉的这个模型的设置最牛逼,第二个是用了内置的优先队列。厉害的我的神,什么时候我能这么厉害就好了。

posted @ 2020-01-07 18:16  就是想学习  阅读(474)  评论(0编辑  收藏  举报