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

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

  • 相关阅读:
    数据库字段太多,批量快速建立实体类方法(适合大量字段建立实体类)
    SQL service 中的 ”输入SQL命令窗口“ 打开了 “属性界面” 回到 ”输入SQL命令窗口“
    计算机软件编程英语词汇集锦
    编程常用英语词汇
    svn上传和下载项目
    当启动tomcat时出现tomcat setting should be set in tomcat preference page
    Implicit super constructor Object() is undefined for default constructor. Must define an explicit constructor
    eclipse中选中一个单词 其他相同的也被选中 怎么设置
    Spring Boot的@SpringBootApplication无法引入的问题
    最全的SpringCloud视频教程
  • 原文地址:https://www.cnblogs.com/sidianok/p/12163081.html
Copyright © 2011-2022 走看看