「BUAA OO Unit 2 HW8」第二单元总结——电梯调度

Part0 前言

0.1 文章简介

本文首先分别分析三次作业中同步块与锁的设置,然后分析调度器以及调度策略,并分析UML类图以及协作图,同时分析各次作业中出现过的bug以及debug方法。最后讲一下本单元的心得体会与对未来的展望。

Part1 第五次作业

1.1 同步块设置 与 锁的选择

本次作业同步的地方主要是在headQueue这个对象身上,也就是所有电梯线程都要从这个对象中取需求,同时输入线程又会向其中加需求。如果对其的访问不放在同步块中的话,有可能会出现对列中的内容一边被修改一边被遍历访问的情况,这时候就可能会抛出异常。那么设置同步块用到的锁就是最基础的synchronized锁。除了headQueue外,并没有对其他对象加锁。

1.2 UML类图 & UML协作图

1.2.1 类图

在这里插入图片描述
我们的主类里的主方法会启动所有的电梯线程(通过building对象间接实现, Building类是当时为了扩展性设计出的,以适应到后面的作业可能会有的增加楼座的操作),并在最后启动输入线程。Input类不断地读取请求并将乘客请求加入到头队列中headQueue(RequestQueue类的对象)。继承了Thread类的电梯类VerElevator的6个电梯线程则从headQueue中读取需求(每一条乘客请求是MyPersonRequest的类对象)来进行运载。

另外,如图所见,值得一提的是,我们这里实现了一个很简单的工厂类createFactory,希望通过工厂模式来提高代码的扩展性。而且,结合往年博客情况看,考虑到后面可能会有横向电梯,所以我也是将本次作业的电梯类的名字加了前缀表示这是纵向电梯,并且留出了相关的接口(比如电梯有一个属性type,这个属性用来表征电梯的类型,也就是区分this电梯是纵还是横),以方便向着纵横电梯同时存在的方向扩展。

1.2.2 UML协作图

在这里插入图片描述对协作图的解释的话,如上面的命名及汉字说明所示。

1.3 作业bug & debug策略

本次作业发现的最为致命的bug是,我“没有处理”超载问题(之所以加了双引号,是因为我处理了,但是却漏写了几句代码,导致程序对电梯剩余容量的判断有误),这导致了强测的全面崩塌。没有别的bug,强测所有错的点都报的是这一个bug。

这里的话没有特殊的debug策略,根据强测结果提示,将对电梯剩余容量检测相关的代码改对后即可通过。

Part2 第六次作业

2.1 同步块设置 与 锁的选择

这次加了维护这一要素,那么我的同步块在hw5的基础上其实就多了一“种”(是一个局部变量,不同的电梯有不同的)对象,那就是“电梯状态变量”------ maintainState,当该值为0时表示电梯并未接收到,否则就是收到了。在程序中我们会及时检测这个状态值,并在需要停止时将电梯停下,将人放出。
每个电梯都有一个maintainState对象,之所以要上锁,是因为每个电梯会访问这个对象,同时这个对象又会被输入线程访问,这就会造成冲突,所以我同样用synchronized锁对该对象进行上锁,让相关线程对其访问同步。

2.2 UML类图 & UML协作图

2.2.1 UML类图

在这里插入图片描述本次作业为了处理维护电梯的情况多加了一些类:MaintainState(这个类对每个电梯都会有一个实例化对象,表示电梯当前的维护状态,我们会对这个局部变量上锁,因为输入线程和各个电梯线程可能同时读写这个对象),同时为了加入了一个单例类 —— SinglePersonList类,这个是我对单例模式的尝试,因为需要一个类来表示是否每个乘客的请求处理是否完毕的情况,这在我的设计中用于判定是否要结束电梯线程,以适应维护电梯这一新的需求。

2.2.2 UML协作图

在这里插入图片描述本次协作图新增了维护和加电梯的元素。

2.3 作业bug & debug策略

本次作业幸运地在测试中没有出现bug。

Part3 第七次作业

3.1 同步块设置 与 锁的选择

本次作业的锁不改变前两次作业的锁,但是和hw6一样,同样新增了锁。使用的也是synchronized锁。增加锁是因为新增了服务中、只接人电梯个数的限制,我用每层楼两个对象变量来分别表示这层楼的服务中和只接人电梯的数量,并且每次有电梯开门时将对象的值修改更新。但是由于多个电梯可能会同时对同一个楼层的对象表示的数量值进行修改,这就可能造成冲突,因此就需要对这些变量进行上锁。好像是自己实现了一把类似信号锁的东西(实验前就写了,当时还没太了解java直接提供的信号锁)。相当于是一把synchronized用到底了。

3.2 UML类图 & UML协作图

3.2.1 UML类图

在这里插入图片描述
本次作业加了一个BranchReq类(这个顾名思义,就是分支请求,也就是考虑了可达性问题,我会把一个总请求拆开,得到一个个分支请求,当然实际上只会使用第一个分支请求,因为后续的分支请求随时可能因为电梯的增删而被更新)和Methods类(我是用这个定义了一些需要用到的方法,比如路径规划方法,掩码解析方法等)。此外还增加了一个Floor类,这是用来实时统计每个楼层当前的只接人电梯和服务中电梯的数量的。各个电梯每次开门(不管是正常开门还是因为维护而停电梯放人时的开门)前,都必须要拿到pickElevators对象和servElevators对象的锁,查看其中的电梯数量,当数量到达限制时就在对应的队列前wait,直到其他处于开门状态的电梯关闭后修改服务中/只接人电梯的数量并notifyAll后再次醒来。

3.2.2 UML协作图

在这里插入图片描述
本次协作图新增了路径规划以及对 只接人/服务中 电梯的控制的要素。

3.3 作业bug & debug策略

本次作业在强测前出现的bug大多都是程序异常跑停的bug,我反思了一下基本都是因为我在hw6的基础上迭代开发时没有把之前的“痕迹”铲除干净导致的(这也提醒我之后再次基于前次作业开发时,要通读一遍代码,看看是否有前面的遗留代码没有处理干净的情况,这将会剩下很多时间)。
解决过的bug是:无限轮询+有限轮询+无限待机(涵盖中测+强测bug)
无限轮询,是电梯对是否应该去拿请求判断错误,导致headQueue中的请求一直不能被取出。
有限轮询,也是拿请求的部分出现bug,但是后面因为有加电梯或者其他的人的进入的波动导致了乘客需求的路径被重新计算,导致之前轮询却无法拿到的乘客请求最终还是能被满足,但是这会导致cpu空转而造成CTLE的bug。
无限待机,这是因为我少加了一个notifyAll(在处理新的 只接人、服务中 电梯数量限制部分的代码中),导致wait的电梯不能按需醒来而错误。

三种bug,debug 的思路一样,debug 的过程比较辛苦,我在电梯类的run方法以及一些其他方法中加了50余个print方法来打印程序的运行信息,这就是我debug 的思路,通过print来将程序的运行流程展现在自己的眼前,让自己直观观察到程序到底是在哪里停了??是为什么停了??是不是无限轮询了??虽然辛苦,但是效果还是很好的,甚至比用idea逐步调试的效果还好,因为自己加print可以随意加自己想要的信息。

Part 4 三次作业的 调度器设计 & 调度策略

4.1 调度器设计

三次作业采取的都是自由竞争的策略,也就是让所有电梯一起去抢头队列的锁,抢到的话就去拿需求,在不超过容量限制的情况下,每次能拿到尽可能多的需求。当然,在第三次作业中,由于一个电梯的可达楼层是有限的,因为只允许某个电梯拿自己能够处理的需求。
所以其实也算是并没有设计调度器,如果真的说实现的话,那就是按照各个电梯的抢活儿本领(线程抢锁)来进行分配请求了。

4.2 调度策略

一百个人有一百个电梯。可能同我一样,每个同学的电梯策略都比较个性化。我是这么想的:电梯去拿需求的时候分两种状态(1)第一种是目前没活儿干,正在躺平。(2)第二种是目前有活儿干,正处于运动的状态。
那么不管哪一种,都是让电梯去头队列中搜索请求并将合适的请求加进自己的需求队列中。各自的《合适的请求》的标准如下:
(1)拿到自己可达起始楼层和目标楼层的,距离电梯当前楼层最近的(可以是两个方向的一起拿)所有“同方向”的请求。(加上可达性的要素后,电梯只能自己同时可达from和to的乘客请求)
(2)拿到和自己运动方向同向,起始,目的楼层都在电梯运动方向上的需求。说白了就是,能捎带就捎带。(加上可达性的要素后,电梯只能自己同时可达from和to的乘客请求)
这样的话效率还行。
当然,在加需求的同时,电梯的目标楼层会随之不断地更新。路过的楼层只要有人要进那就让进(当然,前提是人还没在电梯里),只要有人想出那就让出(前提是人在电梯里面)。
当到达目的楼层且没有新的活可以干的时候,就再次将电梯的运行状态置空,表示现在空闲,然后继续重复上述操作。

Part 5 python自动评测机

5.1 数据生成器

主要说一下第三次作业的数据生成器的构建。第二次的话,只需要保证电梯不被全部维护掉基本就没有什么特殊的点了。第三次作业的话我们需要保证电梯系统时刻可达所有楼层(时刻不出现有不通达两楼层的情况),这个的话,我的思路是加电梯就正常加(捏一个掩码就能符合本次作业的数据要求),但是维护的时候要做点文章,在我想生成维护数据时,我只维护“不破坏全图可达性”的电梯集合中的电梯。这要求我实现一个判断当前电梯系统是否全图可达的函数,我使用bfs实现的,从1个点出发,只要能遍历到所有节点,那就是全图可达的,否则不是。

此外,我以一定概率生成一上来就将6个初始电梯都维护的数据(来对换乘进行压力测试),但是紧接着就会加几个能够保持全图可达性的电梯。这种数据不满足数据合法性要求,但是很显然是不影响测试bug的。

5.2 自动评测机

这个部分的话,就是对照指导书中的“正确性判定”部分,对每个规则一 一写函数进行判定。主函数就是:能通过所有部分的正确性测试,那就输出Accept!!。这个没有太大的算法难度,主要是量大一点,写了600行的python正确性判定程序judge_correct.py

关键由judge_correct.py dataBuilder.py auto_check.py三部分组成。

judge_correct.py:判断一对输入输出是否正确(输入需存放在同目录下的名为stdin.txt的文件中,输出需存放在同目录下名为stdout.txt的文件中)

在所在目录的命令行中输入python judge_correct.py即可判定输入输出是否正确,正确则输出Accepted!!,错误则输出对应的报错信息

dataBuilder.py:数据生成器,其生成的数据会存进同目录下名为stdin.txt的文件中。运行方式同上。

auto_check.py:自动评测机,大致就是写个循环,调用前两个程序,不断地进行这样的过程:数据生成->执行code.jar文件->判断输出是否正确,其负责将历次的评测结果写入log.txt,运行方式同上。(在命令行运行好像有可能卡死,咱也不知道为啥)

缺点在于,我写的这个不支持测超时问题,测试时一般是当评测机卡死了之后,自己跑一下卡死评测机的数据点,如果确实跑不完,那就要去debug了。

Part6 心得体会 & 反思

6.1 心得体会

  因为个人能力实在有限,这个单元对于我来讲是一个不小的挑战。这一单元强测的彻底崩裂,也让我不禁觉得oo是读大学以来遇到的第一门让我感到无力的课(这是真心的感受,co虽然也很难,但是却未曾给过这种感觉)了,主要因为其bug实在是防不胜防,改了无数个bug还有bug,而且加上"多线程+工程量+命令行运行”的因素,每个bug还比较难找,记得评测机捏的一个数据跑出来的bug当时查了5个小时才把那一个数据点跑对,实在是泪了。但是同时呢,我感觉到我的工程能力有了提升,更觉得几百或者上千行代码是稀疏平常了,这个我以为除了多线程的知识外也是一个很棒的收获。

6.2 反思

  1. 为什么这单元的强测会彻底崩裂。反思一下,hw5是因为作业写出来的不及时(记得是周六下午才过了中测),而且这不是主要问题,主要问题是我还没意识到电梯这单元中测和强测强度的差异之大,也没意识到这单元中测过了但是程序仍存在bug 的概率极大极大极大极大极大极大(当然,这是对我个人的水平而言),这直接导致了我并没有给予中测通过后的debug以高度的重视,以至于拖到了最后没时间改bug了。hw6痛定思痛,终于没了bug。但是hw7虽然也足够地重视,用自己亲手写出来的评测机跑了上千条数据点,改了无数个bug,但是却因为没想过(所以评测机也没写关于此bug 的判定)会因为“轮询,但不完全轮询(即,不是死循环的轮询)”的bug导致强测又痛失大片分数。
    这提醒我,要绝对地重视指导书中正确性判定的每一条规则!!!!!!!
  2. 本单元作业,我的自动评测机出现的问题和Unit 1相反,后者是数据生成器生成的数据覆盖不够全面,前者是并未出现数据构造有漏洞而导致的bug,但是却不支持检查ctle的bug,导致强测因此而失去不少分数。

Part7 未来展望

对未来的展望是基于我对过去的反思生发而来的。

  1. oo作业布置后就要把其放在第一优先级,趁早完成,并且直接跑评测机,bug改到差不多后再交中测,不被中测的“通过”迷惑。
  2. 一定一定一定不要疏忽每一条正确性判定规则。因为既然存在这一条,就说明这一条是可能会出错的点!
  3. oo课程过半,希望能始终稳住心态一路顺利走到最后。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值