BUAA-OO-电梯调度
BUAA-OO-电梯调度
1.设计策略
需求分析
设计一个系统,使其可以根据乘客的当前楼层和目的楼层,为乘客分配电梯资源并运送其至目的楼层。
自顶向下
根据需求,可以将整个系统分成三个部分:
- 处理乘客的当前楼层和目的楼层,即输入处理
- 为乘客分配电梯资源,即调度
- 运送至目的楼层,即电梯本体
显然,这三个部分是有次序关系的:乘客输入,处理输入,将请求放入调度队列,调度器从调度队列中取出请求,根据各电梯状态将请求分配给合适的电梯。
上述的过程可以看成是三组 Producer-Consumer 组:
- Producer: 乘客输入,Consumer: 输入处理
- Producer: 输入处理,Consumer: 调度器
- Producer: 调度器,Consumer: 电梯
输入处理
阻塞式获取输入,直到检测到 EOF 或者输入流关闭。处理后的请求存储在 PersonRequest 类中。当获取到合法请求,将请求存入调度队列,唤醒调度器。
调度器
从调度队列中取出请求,根据电梯状态来决定请求被分配至的电梯,对于需要换乘的请求,将后续请求也分配至同一电梯,待前置请求完成,重新对后续请求进行调度。这里的电梯状态包括但不止于电梯能够停靠的楼层、电梯当前运行状态、电梯当前的任务序列、电梯历史运行状态。前两者对于调度起着决定性的作用,而后两者主要是使得各电梯负载均衡。
电梯
从任务序列中取出任务,前往指定楼层执行任务。若完成了某个需换乘请求的前置请求,将后续请求提交给调度器,使其被重新调度。
2.具体实现
类的设置
elevator
├── Direction
├── Dispatcher
├── DivideTask
├── Elevator
├── ElevatorRequest
├── InputHandler
├── Main
├── RequestQueue
├── Task
└── Timer
InputHandler
顾名思义,用于处理乘客输入的类。域中包含有一个调度器(Dispatcher 类实例)。
当获取到合法输入时,唤醒(notify)调度器,否则堵塞。
当输入为 EOF 或者流被关闭时,将调度器的状态设置为停止状态(false),并唤醒(notify)调度器。
Dispatcher
调度器类。域中包含有
- 一个调度队列(RequestQueue 类实例)。
- 各个电梯(Elevator 类实例)。
- 各个电梯的部分状态:能够停靠的楼层、电梯历史运行状态。
当调度队列为空,进入等待状态(wait)。
被唤醒后,从调度队列中依次取出所有请求并进行分配。全部取出后,若状态为运行状态(true),重新进入等待状态(wait);否则,将所有电梯的状态设置为停滞状态(false),并依次唤醒,最后结束调度器线程,但是保留调度器实例。
Elevator
电梯类。域中包含有
- 电梯状态,包括开关门、运行速度,当前楼层,当前目标楼层。
- 两个 Direction 类实例,一个用于表示当前的任务状态(向上或是向下),一个用于表示当前的运行方向。
- 两个 ElevatorRequest 类实例,一个用于存储向上(目标楼层大于出发楼层)的任务,一个用于存储向下(目标楼层小于出发楼层)的任务。
- 一个 LinkedList<DivideTask>类实例,用于存储将要被重新调度的后续请求。
电梯运行总体采用 Look 算法:在不转向的前提下先执行完毕一个方向的任务,然后切换为另一个方向的任务序列,继续执行。执行过程中的等待过程都通过 TImer 类来实现。
Task
任务类。单纯的数据聚合。域中包含有
- 存储任务方向的字符串("IN","OUT")
- 乘客 ID
- 目标楼层
ElevatorRequest
电梯任务类。域中包含有
- 两个 LinkedList<Task>类实例,一个是主任务序列,另一个是副任务序列。
- 一个 Direction 类实例,用于表示任务序列的方向。
当电梯接收到请求,会将请求存入 ElevatorRequest 中。首先进行方向的判断,并将其拆分为两个任务:一个 IN,一个 OUT。然后判断加入主任务序列或是加入副任务序列:若与电梯的任务方向相反,或者可以捎带,则加入主任务序列,否则加入副任务序列。
当主任务序列为空,电梯转向时,进行 fresh 操作,将主任务序列与副任务序列互换。
DivideRequest
待调度请求类。单纯的数据聚合。域中包含有
- 请求(PersonRequest 类实例)
- 目标电梯
3.类图与复杂度分析
类图
如下,与上述的设计描述大致相同。
复杂度
使用 Complexity mertics 生成。
总体而言,复杂度尚可,复杂度较高的几个 method 都是与设计紧密相关的,没有修改或者拆分的必要。
4.自身 bug
在第二次电梯作业中,由于电梯的上下极限从 1-15 变为-3-20,而我在第一次作业中空任务的标志 Magic Number 设置为了 0 忘记修改,导致第二次作业出现了问题。由此可以看出测试的重要性,因而第三次作业我专门写了一个评测机放在服务器上进行长时间测试。
5.心得体会
在多线程编程中,要特别关注暴露在多个线程之外的副作用,严格控制进入临界区的线程数量。