zoukankan      html  css  js  c++  java
  • 使用协程做离散事件仿真。

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

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

  • 相关阅读:
    [LeetCode] 134. Gas Station Java
    [LeetCode] 22. Best Time to Buy and Sell Stock II Java
    [LeetCode] 55. Jump Game Java
    [LeetCode] 264. Ugly Number II Java
    [LeetCode] 331. Verify Preorder Serialization of a Binary Tree Java
    [LeetCode] 232. Implement Queue using Stacks Java
    java.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory的解决办法
    Linux centos 连接网络
    MyEclipse默认编码为GBK,修改为UTF8的方法
    表单
  • 原文地址:https://www.cnblogs.com/sidianok/p/12163081.html
Copyright © 2011-2022 走看看