北航oo第二单元总结

北航oo第二单元总结

第一次作业

1.作业要求

本次作业要求实现单部ALS电梯,模拟电梯的上下行、开关门以及乘客的进出,可以采用任意的稍带策略。

2.实现方法

概述

建立了MainClass、Channel、Elevator1、Queue四个模块。MainClass用于创建输入进程和电梯进程。Channel为输入进程类,负责接收命令。Elevator1为电梯进程类,Queue为调度器,调度器通过分析等待队列和电梯内队列产生对电梯的指令,电梯接受来自调度器的指令向下一个目标楼层运行。

同步块与锁

在Channel类中读取到一条指令后同步将此指令送入等待队列的代码块,因为输入线程只有一个,所以并不存在线程安全问题,这里主要是为了唤醒由于电梯线程没人且没指令时的等待。

在Elevator类中,同步实现如果电梯中没有人且没有等待的指令,则将请求队列阻塞的代码块,与Channel中的唤醒相对应。同时,将除了Morning模式获得下一个楼层之外所有需要运行调度器的代码块都进行同步,以确保调度器分析过程中等待队列不会发生变化,Morning模式例外是因为在Morning模式调度器里需要等待指令输入,Queue需要发生变化。

调度器

我的调度器分为Morning、Night、Random三中模式。

Morning模式为每隔两秒判定一次指令是否增加,如果增加就再等两秒,否则进入楼层分析部分。楼层分析部分第一次先无脑在一楼开一次,把一楼的乘客都装进去,此后遍历已经进入电梯的请求队列,先把下一请求设为第一个请求,不断找到位于当前楼层和下一请求之间的请求设为下一请求,最后返回得到的下一请求对应的楼层。

Night模式如果满六人直接到一楼,否则遍历等待队列,规则与Morning模式相同。

Random模式如果电梯内请求队列长度为零,则只遍历等待队列;如果电梯内人数为六人,则只遍历电梯内请求队列;此外的情况都先遍历等待队列再遍历电梯内请求队列。规则与Morning模式相同。

第一次作业因为只有一部电梯,所以电梯线程与调度器只通过电梯内请求队列进行交互,即电梯线程将自己的inList传入调度器的调度函数中。输入线程将获得的输入传入Queue类中,同样在Queue类中的调度函数直接调用即可。

3.程序BUG

因为是第一次接触多线程,所以最简单的第一次作业反而产生了最多的BUG。

首先是三种模式的轮询超时,最开始我三种运行模式设置的都是如果电梯里没有人且没有没有人在等待队列中则电梯调度器返回当前楼层,而我电梯的运行模式有一个只要还在本楼层且不是第一层就什么都不干直接开始下一次循环。而这就导致在这一层一直轮询导致CTLE,将这一段代码替换为上面提到过的wait-nodifyAll即可。

其次是电梯停止条件的问题,最开始我电梯进程中是while(TRUE)无脑循环,根本不了解电梯需要停下来这件事,随后又改成了电梯内无人且无等待队列,我认为这个条件结合对等待队列的阻塞是可以代替输入结束这一条件的,但事实上我想多了,于是我在Channel类中加了一个end用于指示程序已结束并配备对应getEnd函数。

最后是线程安全问题,刚开始写的时候根本不知道synchronized 究竟是啥,导致电梯线程通过调度器在用queue的时候输入线程一直在往queue里加,导致出错。于是我将除了Morning模式获得下一个楼层之外所有需要运行调度器的代码块都进行同步,解决。本次作业没有出现死锁问题。

在强测中出现Morning模式RTLE的BUG,这个BUG是因为我的Morning模式是一直等的,如果一直在输入即使超过六个人也会等下去,改成达到六个人电梯即开始运行就通过了。

第二次作业

1.作业要求

电梯的运行与第一次作业相同,只是改成了多部电梯并且增加了添加电梯的指令。

2.实现方法

概述

有了第一次作业的基础, 第二次作业相对简单。主要是对于Channel类的修改,一是增加了对电梯添加指令的处理,二是增加了指令分配代码块。此外本次作业为每部电梯配备了对应的等待队列,即Queues类,这个类中综合每个电梯的等待队列成为一个队列集合。为了保证集合元素与电梯的对应关系,我还为电梯设置了另一个序号inIndex,即这部电梯是第几个被建立的。

调度器

因为增加了多个电梯,我在Channel类中实现了一部分调度器的功能。这一部分同样分为Morning、 Night、Random三种模式。

Morning模式在读到一条请求后从第一部电梯开始遍历等待队列集合,如果电梯等待队列中人数没有达到六,则直接将此请求放入其中,否则放入第一部电梯中。

Night模式在读到一条请求后从第一部电梯开始遍历等待队列集合,找到等待人数最少的等待队列,将指令放入其中。

Random模式与Night模式相同。

同步块与锁

第二次作业新增的同步块主要位于Channel类中。首先,同步所有遍历等待队列集合的代码块,这里为等待队列集合加锁以确保在遍历过程中电梯进程不会对其进行操作。在遍历之后得到要添加请求的等待序列,将此序列加锁并同步将请求放入其中的代码块并唤醒此序列。其他与第一次作业相同。

3.程序BUG

有了第一次作业的前车之鉴,第二次作业的BUG少了很多。主要是如下两个:

电梯进程出现了阻塞代码块没有被唤醒的情况,最初我是将唤醒代码写成和第一次作业一样,只是将queue改成了queues,但是发现这样不可行,电梯进程中被阻塞的是对该电梯对应的queue操作代码块而不是queues,这样写唤醒不了,于是改成了对单个queue的唤醒。此外还有一个问题就是输入进程结束后的唤醒,这个也不能无脑直接唤醒queues,而是要遍历queues依次唤醒。

Random模式在强测第八个点被卡了极限时间,这个真的是没有改出来。

第三次作业

1.作业要求

相比于第二次作业,再次增加了不同型号的电梯,不同型号的电梯具有不同的开关门速度、移动速度和限载人数。

2.实现方法

概述

首先是电梯运行部分,在第二次的基础上将电梯运行策略分为三个函数,对应三种不同的电梯。其次是调度器部分,也拆成三个函数,对应三种模式,并在每个函数中为每种电梯写单独的分配策略。

调度器

第三次作业我没有采用换乘策略,因此我并不需要担心电梯内请求队列会出现电梯无法到达的楼层的情况。我只需要将读到的请求进行分类,分别放到三种电梯里即可。具体如下:

在Morning模式下如果输入请求要到达第三层或第十九层,则在等待队列集合中找到等待人数未达到最大载客量的第一部电梯将请求放入该电梯的等待队列中。如果输入请求要到达的层数是奇数且不为三或十九,则在等待队列集合中找到等待人数未达到最大载客量并且不为C型电梯的第一部电梯将请求放入该电梯的等待队列中。如果是其他情况,则放入等待队列集合中人数未达到最大载客量的第一部A型电梯。

在Night模式下如果输入请求要到达第三层或第十九层,则在等待队列集合中找到等待人数最少的第一部电梯将请求放入该电梯的等待队列中。如果输入请求要到达的层数是奇数且不为三或十九,则在等待队列集合中找到等待人数最少并且不为C型电梯的第一部电梯将请求放入该电梯的等待队列中。如果是其他情况,则放入等待队列集合中人数最少的第一部A型电梯。

Random模式与与Night模式相同。

同步块与锁

第三次作业中的同步块与锁与第二次原理相同,只是在不同函数中的同类代码块均进行了同步加锁。

3.程序BUG

第三次作业只有一个小小的BUG,就是Random模式下存在没有被唤醒的情况,原因在于在输入刚好完成后可能会出现电梯中无人且该电梯等待队列无人的情况,此时电梯中的队列会被阻塞且无人来唤醒,导致Random电梯无法结束。

4.可扩展性分析

UML图

UML协作图




第三次作业更多偏向了功能方面,在性能方面没有做过多考虑。从UML协作图中可以看出,无论是电梯线程还是输入线程,调用的都只是Queue和Queues,这就使得整个程序的架构非常简单易懂,可扩展性良好。调度器的两个部分被完全拆开,一部分放在Channel类中,实现输入请求到各个电梯的分配,另一部分放在Queue类中,实现电梯已输入请求的分配。因此一旦出现要更改运行策略,只需要改动在这两个类中的调度函数,其他的不受影响;一旦出现新的电梯类只需要在电梯类中加一种情况即可。这一点从UML图中也可以看出,各个类之间不存在复杂的循环调用,只有queue们在传递着各种信息。

规避死锁的方法

在这三次作业中,我并没有出现死锁情况。在我看来,我之所以规避了死锁,是因为我将Morning模式等待两秒的部分放在了Queue类里而不是Channel类里,而在电梯进程中加锁的是queue,而不是channel,同时,在等待过程中使用的是半轮询的方式,就是硬等两秒看看等待队列增没增加。我的设计思路是电梯进程和输入进程之间没有双向的沟通,即电梯进程中不会对输入进程加锁,输入进程对于电梯进程的影响也仅在于一个nodifyAll,queue成为了两个进程沟通的媒介,因此不可能会出现死锁。

互测

这一单元的评测机实在是写不出来啦。不过我还是通过阅读其他人的代码尝试写了一些数据,无奈整整三个单元一个点都没有hack出来。虽然结果很不理想,但我想说一说我hack的思路。第一步是找出代码中所有的锁分析相互之间的关系找死锁,这个都没有问题不然中测应该过不了。第二步是看我认为最有可能出错的Morning输入部分,看看存不存在输不进去或者是电梯运行不了的情况。第三步是向强测学习卡极限时间(尤其是Morning类)。相比于第一单元的无脑产生数据,这一次的互测需要真正仔细去分析其他人的代码,否则写数据完全无从下手。

心得体会

本单元作业带给我最大的感触就是万事开头难。第一单元的作业极其痛苦,一开始不知道锁怎么用,不知道怎么获取输入,不知道官方包怎么用,不知道怎么模拟电梯运行,也不知道进程究竟是个啥。但是在看了整整一天的多线程教程,学长代码,官方包后,特别是在那一周周五的实验过后,一切都豁然开朗,之后的第二第三次作业也完成的很轻松。

写多线程,一定要细心,写出一个大体框架之后一定要把代码之间的逻辑完完整整地梳理一遍, 弄清楚每一个被阻塞的进程是不是一定能被唤醒,判断有没有死锁问题,一定要确定线程终止的条件究竟是什么,看好一个函数究竟有没有轮询。

当然,第二单元作业也有小小的遗憾,就是没有进行系统的优化。清明时事情太多耽搁了一周,就一直没能仔细研究研究电梯调度算法。不过正所谓简单的未必是差的,这种最简单的调度算法还是有一半的测试点性能分很高,这也算是意外之喜。

posted @ 2021-04-27 09:20  phm19231049  阅读(86)  评论(0编辑  收藏  举报