这个简单的例子让我们比较浅显易懂的看到了事件驱动型框架的运作方式,即在单个线程中使用一个主循环驱动协程执行并发活动。
使用协程做面向事件编程时,协程会不断的把控制权让步给主循环,激活并向前运行其他协程,从而执行各个并发活动。这是一种协作多任务:协程显示的把控制权让步给中央调度程序。
仿真示例
import randomimport collectionsimport queueimport argparseimport timeDEFAULT_NUMBER_OF_TAXIS = 3DEFAULT_END_TIME = 180SEARCH_DURATION = 5TRIP_DURATION = 20DEPARTURE_INTERVAL = 5Event = collections.namedtuple('Event', 'time proc action')#出租车进程。def taxi_process(ident, trips, start_time=0): """每次状态变化时向仿真程序产出一个事件""" 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') #结束出租车进程#出租车仿真程序主程序。class Simulator: def __init__(self, procs_map): self.events = queue.PriorityQueue() #优先级队列,put方法放入数据,一般是一个数组(3, someting),get()方法数值小的优先出队 self.procs = dict(procs_map) #创建字典的副本 def run(self, end_time): """调度并显示事件,直到事件结束""" #调度各辆出租车的第一个事件 for _, proc in sorted(self.procs.items()): first_event = next(proc) #第一个事件是所有车离开车库,也是为了激活子生成器 self.events.put(first_event) #所有车的第一个事件放到优先队列中,time小的优先出来 #此次仿真的主循环 sim_time = 0 while sim_time < end_time: if self.events.empty(): print('***事件结束***') break current_event = self.events.get() #取出time最小的事件 sim_time, proc_id, previous_action = current_event #元组解包 print('taxi:', proc_id, proc_id * ' ', current_event) active_proc = self.procs[proc_id] #取出当前事件对象。是一个子生成器对象,下面要对这个对象send(time)来获得下一个yield的返回值 next_time = sim_time + comput_duration(previous_action) #随机计算下一个时间 try: next_event = active_proc.send(next_time) #下一个事件是子生成器执行到下一个yield的返回值 except StopIteration: #StopIteration异常说明当前子生成器执行完毕,从字典中删除它 del self.procs[proc_id] else: self.events.put(next_event) #否则就把下一个事件放入优先队列中 else: #如果while循环没有以break结束,那么输出结束信息 msg = '*** 仿真结束。{}个车没有回家 ***' print(msg.format(self.events.qsize())) #仿真结束def comput_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('未知的活动:{}'.format(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) #指定seed的值时,用这个seed可以使随机数随机出来的相等 taxis = {i: taxi_process(i, (i+1*2), i*DEPARTURE_INTERVAL) for i in range(num_taxis)} #字典生成式,生成指定数量的子生成器对象 sim = Simulator(taxis) #实例化仿真主循环 sim.run(end_time) #run it!if __name__ == '__main__': parser = argparse.ArgumentParser(description='出租车运行仿真') #创建参数解析对象,添加描述 parser.add_argument('-e', '--end_time', type=int, default=DEFAULT_END_TIME) #添加-e参数,默认值为180 parser.add_argument('-t', '--taxis', type=int, default=DEFAULT_NUMBER_OF_TAXIS, help='出租车出行数量, default=%s' %DEFAULT_NUMBER_OF_TAXIS) #添加-t参数,用来指定出租车数量,默认值为3 parser.add_argument('-s', '--seed', type=int, default=None, help='随机生成seed') #添加-s参数,用来设置seed值,如果seed值一样那么随机出来的结果也会一样,默认值为None args = parser.parse_args() #这个函数用来获取参数 main(args.end_time, args.taxis, args.seed) #通过上面函数的属性的到输入的参数,属性可以是双横线后的字符串也可以是添加参数函数的第一个不加横线的字符串