出租车队运营仿真

创建几辆出租车,每辆车会拉几个乘客,然后回家。出租车首先驶离车库,四处徘徊,寻找乘客;拉到乘客后,行程开始;乘客下车后,继续四处徘徊。

程序解释

程序的输出示例:

创建 3 辆出租车的输出示例。-s 3 参数设置随机数生成器的种子,这样在调试和演示时可以重复运行程序,输出相同的结果。不同颜色的箭头表示不同出租车的行程。

最值得注意的一件事是,3 辆出租车的行程是交叉进行的。那些箭头是我加上的,为的是让你看清各辆出租车的行程:箭头从乘客上车时开始,到乘客下车后结束。有了箭头,能直观地看出如何使用协程管理并发的活动。

注意:

1.出租车每隔 5 分钟从车库中出发。
2. 0 号出租车 2 分钟后拉到乘客(time=2),1 号出租车 3 分钟后拉到乘客(time=8),2 号出租车 5 分钟后拉到乘客(time=15)。
3. 0 号出租车拉了两个乘客(紫色箭头):第一个乘客从 time=2 时上车,到 time=18 时下车;第二个乘客从 time=28 时上车,到
time=65 时下车——这是此次仿真中最长的行程。
4. 1 号出租车拉了四个乘客(绿色箭头),在 time=110 时回家。
5 2 号出租车拉了六个乘客(红色箭头),在 time=109 时回家。这辆车最后一次行程从 time=97 时开始,只持续了一分钟。
6.1 号出租车的第一次行程从 time=8 时开始,在这个过程中 2 号出租车离开了车库(time=10),而且完成了两次行程(那两个短的
红色箭头)。
7. 在此次运行示例中,所有排定的事件都在默认的仿真时间内(180分钟)完成;最后一次事件发生在 time=110 时。

主程序

taxis = {0: taxi_process(ident=0, trips=2, start_time=0),
1: taxi_process(ident=1, trips=4, start_time=5),
2: taxi_process(ident=2, trips=6, start_time=10)}
sim = Simulator(taxis)
sim.run(end_time)

taxi_process 协程,实现各辆出租车的活动

def taxi_process(ident, trips, start_time=0):  #每辆出租车调用一次 taxi_process 函数,创建一个生成器对象,表示各辆出租车的运营过程。
#ident 是出租车的编号(如上述运行示例中的 0、1、2);trips 是出租车回家之前的行程数量;
#start_time是出租车离开车库的时间。
"""每次改变状态时创建事件,把控制权让给仿真器"""
time = yield Event(start_time, ident, 'leave garage') #产出的第一个 Event 是 'leave garage'。执行到这一行时,协程
#会暂停,让仿真主循环着手处理排定的下一个事件。
#需要重新激活这个进程时,主循环会发送(使用 send 方法)当前的仿真时间,赋值给time。
for i in range(trips): #每次行程都会执行一遍这个代码块。
time = yield Event(time, ident, 'pick up passenger') #产出一个 Event 实例,表示拉到乘客了。
#协程在这里暂停。需要重新激活这个协程时,主循环会发送(使用 send 方法)当前的时间。
time = yield Event(time, ident, 'drop off passenger') # 产出一个 Event 实例,表示乘客下车了。协程在这里暂停,等待主循环发送时间,然后重新激活
yield Event(time, ident, 'going home') #指定的行程数量完成后,for 循环结束,最后产出 'going home'事件。
#此时,协程最后一次暂停。仿真主循环发送时间后,协程重新激活;不过,这里没有把产出的值赋值给变量,因为用不到了。
# 出租车进程结束 协程执行到最后时,生成器对象抛出 StopIteration 异常。

这个协程用到了别处定义的两个对象:compute_delay 函数,返回单位为分钟的时间间隔;Event类,一个 namedtuple,定义方式如下:

Event = collections.namedtuple('Event', 'time proc action')

Simulator 类的主要数据结构如下

self.events
  PriorityQueue 对象,保存 Event 实例。元素可以放进(使用put 方法)PriorityQueue 对象中,然后按 item[0](即 Event 对象
的 time 属性)依序取出(使用 get 方法)。
self.procs
  一个字典,把出租车的编号映射到仿真过程中激活的进程(表示出租车的生成器对象)。这个属性会绑定前面所示的 taxis 字典副本。

Simulator.run 方法实现的算法:

(1) 迭代表示各辆出租车的进程。
  a. 在各辆出租车上调用 next() 函数,预激协程。这样会产出各辆出租车的第一个事件。
  b. 把各个事件放入 Simulator 类的 self.events 属性(队列)
中。
(2) 满足 sim_time < end_time 条件时,运行仿真系统的主循环。
  a. 检查 self.events 属性是否为空;如果为空,跳出循环。
  b. 从 self.events 中获取当前事件(current_event),即PriorityQueue 对象中时间值最小的 Event 对象。
  c. 显示获取的 Event 对象。
  d.获取 current_event 的 time 属性,更新仿真时间。

  e.把时间发给 current_event 的 proc 属性标识的协程,产出下一个事件(next_event)。
  f.把 next_event 添加到 self.events 队列中,排定next_event。

代码

#taxi_sim.py
class Simulator: def __init__(self, procs_map):
self.events = queue.PriorityQueue() #保存排定事件的 PriorityQueue 对象,按时间正向排序。
self.procs = dict(procs_map)# 获取的 procs_map 参数是一个字典(或其他映射),可是又从中构建一个字典,创建本地副本,
# 因为在仿真过程中,出租车回家后会从self.procs 属性中移除,而我们不想修改用户传入的对象。
def run(self, end_time):#run 方法只需要仿真结束时间(end_time)这一个参数。
"""排定并显示事件,直到时间结束"""
# 排定各辆出租车的第一个事件
for _, proc in sorted(self.procs.items()): #使用 sorted 函数获取 self.procs 中按键排序的元素;用不到键,因此赋值给 _。
first_event = next(proc) #调用 next(proc) 预激各个协程,向前执行到第一个 yield 表达式,做好接收数据的准备。产出一个 Event 对象。
self.events.put(first_event) #把各个事件添加到 self.events 属性表示的 PriorityQueue 对象中。
#如示例 16-20 中的运行示例,各辆出租车的第一个事件是 'leave garage'
# 这个仿真系统的主循环
sim_time = 0 #把 sim_time 变量(仿真钟)归零。
while sim_time < end_time: #这个仿真系统的主循环:sim_time 小于 end_time 时运行。
if self.events.empty(): #如果队列中没有未完成的事件,退出主循环。
print('*** end of events ***')
break
current_event = self.events.get() #获取优先队列中 time 属性最小的 Event 对象;这是当前事件(current_event)。
sim_time, proc_id, previous_action = current_event #拆包 Event 对象中的数据。这一行代码会更新仿真钟 sim_time,对应于事件发生时的时间。
print('taxi:', proc_id, proc_id * ' ', current_event) #显示 Event 对象,指明是哪辆出租车,并根据出租车的编号缩进。
active_proc = self.procs[proc_id] #从 self.procs 字典中获取表示当前活动的出租车的协程。
next_time = sim_time + compute_duration(previous_action) #调用 compute_duration(...) 函数,传入前一个动作
#(例如,'pick up passenger'、'drop off passenger' 等),
#把结果加到 sim_time 上,计算出下一次活动的时间。
try:
next_event = active_proc.send(next_time) #把计算得到的时间发给出租车协程。
#协程会产出下一个事件(next_event),或者抛出 StopIteration 异常(完成时)
except StopIteration:
del self.procs[proc_id] #如果抛出了 StopIteration 异常,从 self.procs 字典中删除那个协程。
else:
self.events.put(next_event) #否则,把 next_event 放入队列中。
else: #如果循环由于仿真时间到了而退出,显示待完成的事件数量(有时可能碰巧是零)
msg = '*** end of simulation time: {} events pending ***'
print(msg.format(self.events.qsize()))

注意,
1.主 while 循环有一个 else 语句,报告仿真系统由于到达结束时间而结束,而不是由于没有事件要处理而结束。
2.靠近主 while 循环底部那个 try 语句把 next_time 发给当前的出租车进程,尝试获取下一个事件(next_event),如果成功,执
行 else 块,把 next_event 放入 self.events 队列中。

这个示例的要旨是说明如何在一个主循环中处理事件,以及如何通过发送数据驱动协程。

python 并发专题(五):离散事件仿真(事件循环生成器)的更多相关文章

  1. python并发编程之进程2(管道,事件,信号量,进程池)

    管道 Conn1,conn2 = Pipe() Conn1.recv() Conn1.send() 数据接收一次就没有了 from multiprocessing import Process,Pip ...

  2. python 并发专题(一):并发基础相关概念,术语等

    一.线程 1.概念 线程是程序执行流的最小执行单位,是行程中的实际运作单位. 进程是一个动态的过程,是一个活动的实体.简单来说,一个应用程序的运行就可以被看做是一个进程,而线程,是运行中的实际的任务执 ...

  3. python 并发专题(十四):asyncio (三)实战

    https://www.cnblogs.com/wongbingming/p/9124142.html 在实战中,将会用到以下知识点: 多线程的基本使用 Queue消息队列的使用 Redis的基本使用 ...

  4. python 并发专题(十三):asyncio (二) 协程中的多任务

    . 本文目录# 协程中的并发 协程中的嵌套 协程中的状态 gather与wait . 协程中的并发# 协程的并发,和线程一样.举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口 ...

  5. python 并发专题(二):python线程以及线程池相关以及实现

    一 多线程实现 线程模块 - 多线程主要的内容:直接进行多线程操作,线程同步,带队列的多线程: Python3 通过两个标准库 _thread 和 threading 提供对线程的支持. _threa ...

  6. python 并发专题(十三):asyncio (一) 初识

    https://www.cnblogs.com/wongbingming/p/9095243.html . 本文目录# 如何定义/创建协程 asyncio的几个概念 学习协程是如何工作的 await与 ...

  7. python 并发专题(四):yield以及 yield from

    一.yield python中yield的用法很像return,都是提供一个返回值,但是yield和return的最大区别在于,return一旦返回,则代码段执行结束,但是yield在返回值以后,会交 ...

  8. python 并发专题(六):协程相关函数以及实现(gevent)

    文档资源 http://sdiehl.github.io/gevent-tutorial/ 一.协程实现 线程和协程 既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程: 线程之间需要上下 ...

  9. python 并发专题(三):进程以及进程池相关以及实现

    一.多进程实现 multiprocess.process模块 process类 Process([group [, target [, name [, args [, kwargs]]]]]),由该类 ...

随机推荐

  1. Quartz SpringBoot 简单整合一下

    一次简单的代码整合记录. 数据库准备 如果是MySQL可能出现一些小问题.比如联合主键长度超限制,已经记录解决办法了. CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NA ...

  2. Source insight 提示: it is not currently available for write access

    点击阅读原文 使用sourceinsight编辑linux内核文件后不能保存,并且弹出窗口揭示:Error: "Z:\linux\kernel\kernel-2.6.13\scripts\k ...

  3. 给博客使用Butterfly主题并部署到GitHub服务器

    目录 前言 一.安装Butterfly主题 二.将本地博客部署到GitHub服务器 三.将个人域名与GitHub绑定 前言 安装完Hexo框架后,自带的主题在thems文件夹下可以查看,应用后界面: ...

  4. 2019-02-02 Python学习之多线程

    1.主线程和次线程 若主线程结束则次线程也会结束 如何避免主线程先结束: 结尾处加上 while True: pass e.g. import win32api #引用系统函数 import _thr ...

  5. WeChair项目Alpha冲刺(9/10)

    团队项目进行情况 1.昨日进展    Alpha冲刺第九天 昨日进展: 前端:安排页面美化,设计实名认证 后端:更新dao层代码 数据库:修改数据表属性,与后端部署数据库交互 2.今日安排 前端:继续 ...

  6. Jenkins入门-环境搭建(1)-转

    因为Jenkins的环境搭建比较简单,本来不想来介绍,但是发现有些入门小朋友,从各种网站上下载的各种安装包来搭建,最后导致出现了各种千奇百怪的问题,介于这种情况下我决定还是来写一下Jenkins的环境 ...

  7. 多线程高并发编程(12) -- 阻塞算法实现ArrayBlockingQueue源码分析(1)

    一.前言 前文探究了非阻塞算法的实现ConcurrentLinkedQueue安全队列,也说明了阻塞算法实现的两种方式,使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出 ...

  8. web安全之跨站脚本漏洞(XSS)

    XSS(跨站脚本)概述以及pikachu上的实验操作 Cross-Site Scripting 简称为“CSS”,为避免与前端叠成样式表的缩写"CSS"冲突,故又称XSS. XSS ...

  9. JavaScript图形实例:窗花图案

    1.窗花基本框线 设定曲线的坐标方程为: n=25; r=100; x=r/n*cos(5*θ)+r*cos(θ); y=r/n*sin(5*θ)+r*sin(θ);          (0≤θ≤2π ...

  10. git配置用户和邮箱

    1. 查看git用户配置 git config user.name 2. 查看git邮箱配置 git config user.email 3. 配置git用户 git config --global ...