OO第二单元作业总结

第一次作业

第一次作业的要求为使用多线程实现单部ALS电梯,在架构时主要考虑的问题怎样设计线程。通过课上学习和讨论区的分享,选择使用生产者消费者模式,一个线程为指令处理线程(生产者),另一个线程为电梯线程(消费者),建立“电梯乘客表”类(缓冲区),实现电梯调度。

第一次作业的实现思路

本次作业主要由三部分组成:电梯线程、数据处理线程和乘客表。

  1. 电梯线程:电梯线程主要负责从乘客表将乘客取出电梯,并将乘客带到目标楼层。电梯主要有三种工作状态:

  • 运行状态:在楼层间运行,每层400ms,到达后输出楼层信息。运行状态是电梯工作时的主要状态,只要有乘客在电梯就会保持运行。

  • 停靠状态:当电梯到达一个楼层时,需要判断是否需要在本楼层停靠,停靠条件有:

    • 电梯内有乘客下电梯。

    • 电梯未满,电梯外有乘客要上电梯。 停靠时间为400ms,先下后上,保证电梯人数不大于满载人数。

  • 等待状态:如果乘客表中没有乘客,也没有收到“输入结束”的标志,电梯在运送最后一名乘客的楼层关门等待,直到有新乘客出现。

电梯运行的控制由电梯的运行方向、电梯所在楼层和电梯的目标楼层共同控制。电梯的运行方向为电梯目标楼层和所在楼层的相对方向,当电梯所在楼层和电梯目标楼层相同时,更新电梯目标楼层,更新原则为:

  • 电梯内部有人时,电梯目标楼层为内部队列第一个乘客的目标楼层。

  • 电梯内部无人时,电梯目标楼层为乘客表总队列中第一个乘客所在楼层。

  1. 数据处理线程:获得输入指令,将指令转化成乘客类,放入乘客表。

  2. 乘客表:乘客表负责维护乘客候乘队列,并维护线程安全。

线程安全维护

基于生产者消费者模式,本次作业的线程安全问题主要是电梯线程和数据处理线程对乘客候乘队列的改写,在设计线程保护时,选择只在管理乘客表的类中进行加锁,而不在线程中加锁。乘客表类中,调用从乘客表取人进入电梯和从输入指令中将人放入乘客表这两个函数时,需要将乘客队列进行加锁。为了避免轮询,当电梯和乘客队列没有人时电梯线程挂起,直到有人加入队列再唤醒。

调度器设计

由于只有一部电梯,故没有实现调度器。

程序结构分析

UML类图

UML协作图

 

可以看出,本次作业使用了经典的生产者消费者模式,考虑的实际情况为:当一个乘客上电梯后,如果没有别人,电梯立刻关门运送该乘客。这种方法有明显的性能缺陷,比如说,1楼乘客要去20楼,1秒后又来了一个1楼乘客要去20楼,电梯的调度会是将第一个人送至20楼后再回到1楼运第二个人,而最优方法是等待两个人同时上电梯再去20楼。本次作业有早、晚、普通三种电梯,早电梯可以通过让人在一楼上满再出发来实现最优;晚电梯通过实现从最高层开始向下捎带实现最优;但普通电梯在无法预知未来的情况下并没有最优,因此本次作业选择放弃让电梯“预知未来”,用牺牲性能换取电梯设计的合理性。

BUG

本次作业的bug是电梯最多人数设置为7,应该是6。

第二次作业

第二次作业为多部电梯,且可以后加电梯,除了延续生产者消费者模式的单部电梯运行策略外,设置一个调度器类来为每一步电梯的乘客表分配乘客。

线程安全维护

本次作业的线程安全问题和上一次相同,都是对乘客表的添加和删除乘客造成的线程安全,除此之外本次作业的线程安全问题还有将现有的候乘乘客移到新的电梯乘客表中,在移动乘客时将乘客队列加锁即可。

调度器设计

本次作业的调度原则为:

  1. 如果这个乘客所在楼层有电梯且电梯没人,将这个乘客放入该电梯。

  2. 如果这个乘客能被这个电梯捎带,将这个乘客放入该电梯。

  3. 如果上述两种情况都不符,放入乘客最少的电梯。

程序结构分析

UML类图

UML协作图

这种调度方法的性能问题同样与“未来”有关:比如,有四个人要从1楼到20楼,并且到达1楼时间间隔为1秒,那么三部电梯会将前三个人运到20楼,第一部电梯再回到一楼接第四个人,实际上最优解为第一和第二个人上第一个电梯,后两个人分别上剩下的电梯。本次作业不考虑“未来”发生的情况,只对乘客进行简单调度,在加电梯后也不进行太复杂的优化,直接将别的电梯乘客分给新加的电梯实现人数平均。尽可能让电梯都保持工作状态。

BUG

本次作业的BUG为极限数据点(180s才开始来人)有概率通过,此外由于乘客移动可能导致电梯到达楼层接不到人,可能导致电梯方向和目标相反,在运行状态添加特判,遇到方向问题及时调整,以保证电梯可以到达目标。

第三次作业

第三次作业为不同电梯换乘,为乘客增加了换乘功能。

线程安全维护

本次作业的线程安全问题和上一次相同,都是对乘客表的添加和删除乘客造成的线程安全,除此之外本次作业的线程安全问题还有将换乘乘客移到新的电梯乘客表中,在移动乘客时将乘客队列加锁即可。

调度器设计

本次作业的调度原则为:

  1. 如果乘客的出发和目标楼层都在[1-3][18-20],坐C电梯

  2. 如果乘客的出发楼层在[1-3][18-20]且目标楼层距离出发楼层较远,且目标楼层为偶数,坐C转A。

  3. 如果乘客的出发楼层在[1-3][18-20],目标楼层距离出发楼层较远,且目标楼层为奇数,坐C转B。

  4. 如果乘客出发楼层和目标楼层都为奇数,做B电梯。

  5. 如果乘客出发楼层为奇数目标楼层为偶数,做B转A。

  6. 不符合上述5种,做C电梯

以上规则的优先级从上到下递减。 对于相同型号电梯的选择,由于电梯数量有限,直接进行平均分配。

程序结构分析

UML类图

UML协作图

本次作业对性能的影响主要是如何让乘客进行换乘。即使不知道“未来”的情况,想要计算最优的换乘方法需要枚举所有电梯的运行状态,极其复杂,因此本次作业不考虑电梯的运行情况,只考虑乘客的需求,能做快梯尽量坐快梯,换乘最多换两次,使换乘策略尽可能简单,不容易产生bug。

BUG

本次作业未发现bug

心得体会

本单元作业让我体会了多线程的使用。多线程的使用主要需要考虑线程设计和线程安全,线程设计方面,这次作业让我体会了生产者消费者模式的妙用,多线程编程的模式还有很多,以后还要多多了解学习。线程安全方面,为了避免线程轮询长时间占据CPU,需要让线程在特定条件挂起,而挂起后不去唤醒就会导致线程死锁,因此要全方面考虑线程的安全问题。本单元的第一次作业是最难的一次,我在做第一次作业时经历了死锁、轮询等多种多线程问题。为了debug,我为函数添加了许多输出,如果出现一个输出持续出现,就说明发生轮询了。输出信息也可以帮我了解当前电梯的运行状态,帮助我修改电梯运行策略的bug。同时,评论区助教关于避免轮询的讲解对很我完成作业有着极大的帮助,也让我改善了代码结构,将线程安全维护从线程移到外部,大大降低程序的耦合度。

posted @ 2021-04-24 13:52  李鸿洋  阅读(76)  评论(0)    收藏  举报