BUAA OO UNIT2 目的选层电梯单元总结
BUAA OO UNIT2 目的选层电梯 单元总结
同步块&锁
hw5
在等待队列Qu上的锁上,输入线程和调度线程成为一对生产-消费关系。而调度线程和电梯在Rlist上的生产-消费关系则由Rlist自身的同步方法保证线程安全。电梯对象的锁在本次作业是没有使用必要的,但为了后续作业考虑,还是使用了电梯对象锁。ElvMotor运行时会被创建为一个电梯的守护线程,负责处理开关门、上下楼的时序问题,内部所有方法都是synchronized方法,电梯通过暴露的cmd方法操作该线程。
类 | Qu | Rlist | Elv | ElvMotor |
---|---|---|---|---|
IN | run() | |||
Trans | Close(), tr(), run() | |||
Rlist | add(), done(), qry(), poll() | |||
Elv | setClsd(), add(), done(), redir(),port(), run() | redir(), port(), wk(), run() | ||
ElvMotor | flr(), gate(), up(), dn(), open(), close(), cmd(), run() |
hw6
输入线程和调度器的关系与hw5相同,但由于采用了共享等待队列的架构,Qu上还增加了属于电梯线程的临界区。为防止调度器同时调度多个电梯造成混乱,调度器内部launch等方法被设计为synchronized。电梯内部的诸多方法也被设计为synchronized,以防止调度器修改电梯参数时发生混乱。ElvMotor守护线程与hw5相同。
Qu | Trans | Elv | ElvMotor | |
---|---|---|---|---|
IN | run() | |||
Trans | close(), launch(), nalloc(), run() | Newelv(), launch(), run() | launch() | |
Elv | porti(), run() | wtf(), dir(), launch(), tp(), dis(), done(), ori(),wk(), run() | ||
ElvMotor | flr(), gate(), cmd(), up(), dn(), run() |
hw7
输入线程不再直接操作等待队列,而是通过调用Base提供的方法修改等待队列,同步块更集中。将ElvMotor改为Elv的private类,不再作为守护线程独立存在。
Qu | Elv | Elv.Motor | |
---|---|---|---|
Base | req(), alloc() | alloc() | |
Elv | release() | port() | go(), open(), close() |
调度器&线程交互
hw5
调度器仅负责把请求从主等待队列转储到电梯等待队列中,直接用synchronized块解决即可。
hw6
调度器负责把电梯“发射”至目标楼层,目标楼层可以是出现请求的楼层,也可以是约定好当没有可分配请求时应去往的停泊楼层。
调度器有主动入口和被动入口:新增请求时输入线程激活调度器,使用主动入口;电梯轿厢为空时调用调度方法,使用被动入口。
hw7
调度器每隔200ms扫描一遍等待队列,刷新每个请求分配的电梯号码,刷新每个电梯的目标楼层。依次trylock所有电梯,若成功则signal()。除sleep(200)外,调度器永不休眠,因而也不需要被唤醒。调度器给每个电梯设置目标楼层时赋值语句是原子语句,不需要加锁。
平衡&可拓展性
hw5
电梯自身调度是魔改的莫队算法,希望利用请求出现的局部性,优先在一段小区间里往返送人。人少的时候效果极差。类图中Rlist类是莫队实现的主要基础。在此基础上增加电梯非常容易,只要继承电梯类,并重写与停靠楼层、运行速度相关的方法即可,调度器也只需改为转储时向不同电梯写入请求即可。
性能:失败;可拓展性:良好。
![diagram5](https://img2020.cnblogs.com/blog/2285223/202104/2285223-20210423192327726-1014073015.png)
hw6
使用共享等待队列,Rlist被弱化为仅包含判断方向是否相同、比较距离远近的静态方法。共享队列 + 调度器分配目标楼层 的架构兼具抢人和分配两种思路的优势,性能优秀。但共享队列带来了一些线程安全问题,其解决较复杂。由于调度时认为各电梯之间没有属性上的差异,因此增加电梯种类可能导致调度器出现较大改动。
性能:优秀;可拓展性:一般。
![diagram6](https://img2020.cnblogs.com/blog/2285223/202104/2285223-20210423192712548-1735540394.png)
hw7
共享队列plus。调度器每隔200ms刷新一次,重新分配请求,设置每部电梯的目标楼层。每个请求增加权限部分,可以只允许指定电梯载客,考虑了每部电梯之间属性差异。通过删除调度器唤醒电梯以外的所有唤醒操作、删除与等待队列或电梯线程无关的所有临界区,成功杜绝了线程安全问题。实现了简单的换乘,但总体性能似乎没有不换乘好。
性能:较优秀;可拓展性:尚可。
![sequence7](https://img2020.cnblogs.com/blog/2285223/202104/2285223-20210423193103201-1926437514.png)
bug
hw5
- 没有发现bug
hw6
-
没有互测中发现的bug
-
强测第9个点运行过慢的"bug"
-
特征:贪心算法在极端情况下失效
Class CogC ev(G) iv(G) v(G) Trans.nearq() 5 4 2 5 -
位置:Trans(调度器)类,nearq方法
-
修复:qu.size()>=6则改成裸的抢人
-
-
本地以1/30的频率稳定复现调度器唤醒电梯时被阻塞,导致电梯请求调度时发生死锁的bug。
-
特征:在需要系统原语的地方使用了两步判定
Class CogC ev(G) iv(G) v(G) Trans.run() 22 4 10 11 -
位置:Trans(调度器)类,run方法
-
修复:改用trylock()
-
hw7
-
print过于靠后的bug
-
特征:代码长度7,圈复杂度1
Class CogC Ev(G) iv(G) v(G) Elv.Stop.release() 1 1 2 2 -
位置:Elv(电梯类)->Stop子类,release方法
-
修复:print语句移到该方法第一行
-
-
分配时发生数组越界的bug
-
特征:未考虑参数在执行过程中被其他线程修改的情况
Class CogC ev(G) iv(G) v(G) Trans.run() 39 8 13 16 -
位置:Base(调度器)类,alloc方法
-
修复:cnt改为tmp.length
-
分析别人bug
- 测试策略
- 黑盒测试,一次运行30个子进程
- 有效性:模拟评测机运行情况,并发现了强测未发现bug
- 线程安全相关
- 所有测试数据均为随机生成,实际上由于线程安全问题的不确定性,我不认为手动构造有明显效果
- 同时运行多个测试进程,增大了线程安全问题出现的概率
- 与第一单元的差异
- 第一单元hack重心在于正确性判定,第二单元重心在于线程安全问题。
- 第一单元数据构造策略显著影响hack效率,需要用心构造高强度数据。
- 第二单元如何运行多进程,如何判定CTLE和RTLE,如何杀死TLE的死锁进程显著影响hack效率。
心得
共享队列的idea
相比于在两级队列之间做分配和重配,不如只做一个共享的队列来得方便。然而,这显然会导致一些线程安全问题。
如何解决呢?我选择减少线程间的通信,并且禁止多个对象在同一个锁上调用wait()方法。这样已经解决了绝大部分线程安全问题,余下的问题是:调度器尝试激活电梯时电梯会小概率自行激活,导致调度器被阻塞,后续电梯notify调度器,请求调度时会发生死锁。
造成这个问题的原因是调度器已判断电梯处于WAITING状态,又尚未激活电梯时,电梯自行唤醒,使调度器被阻塞。这时将if判断放进同步块只会增大阻塞发生概率,不能解决问题。于是我在本单元第三次作业改用了tryLock()方法,解决了这个问题。
调度算法实现
只有调度器发射某个电梯后,该电梯才允许载客。电梯在被发射后运行look算法,不断捎带有权限捎带的乘客,直到轿厢为空,而调度器又未给该电梯分配新的目标楼层。这时电梯进入空闲状态,不再载客。
本单元第二次作业调度器:贪心地给电梯分配相比其他电梯都更近的乘客所在楼层,如果不存在,则分配一个预先算好的停泊楼层(每20/总电梯数量 个楼层设一个停泊楼层)。这个策略在真随机数据下性能良好,但在特殊构造的数据下容易被卡。一个临时补丁是:如果等待队列里人数>6则退化回暴力抢人。
第三次作业改进
改用trylock()之后,我令调度器每200ms刷新一次等待队列,重新分配请求。这样,调度器不再需要被输入线程或电梯线程激活,降低了架构复杂度。电梯的目标楼层每200ms刷新一次,应该能收获更好的性能。同时,调度器分配请求时,优先从最小到达时间最长的请求开始分配,避免了贪心算法在极端数据下失效的问题。
第三次作业中还出现了电梯与调度器不同步的问题,电梯运行一层的时间会略大于k*200ms,导致需要多等待200ms才能被激活,结果就是所有电梯上升/下降一层的时间都增加了0.2s。解决方法是尽量让电梯不休眠,不再每运行一层就等待调度,而是直到轿厢为空才进入休眠。这样就大大降低了以上情况出现的几率。
![IMG_0164](https://img2020.cnblogs.com/blog/2285223/202104/2285223-20210423194042732-1813332296.jpg)