OO Unit2 总结
前言
第二单元主要涉及到电梯的调度,运行策略,电梯之间的关系,电梯和调度器之间的关系。在这之中只要把线程之间的关系理清楚,防止死锁,就能很好的处理出这些关系。但是多线程编程比之前的单线程编程最难的一点就在于处理线程之间的关系,一边梳理逻辑,一边梳理线程之间的关系,在梳理逻辑时又习惯了用单线程的逻辑,就很容易导致在写代码的时候忽略了考虑死锁的可能性,最后导致我在第6次和第7次的作业中都出现了死锁的问题,在强测中扣了很多分,还有在第6次互测中成为了地雷。
如果忽略死锁的问题,单从策略和性能,以及可拓展性的角度分析,我的代码还是做的十分不错的,在第六次策略中使用了影子电梯策略,并且只在第五次作业的基础上添加了少数代码就实现了影子电梯,可见第5次作业的可拓展性是十分不错的,在室友写的性能测试跑分中也跑出了十分不错的成绩,强测中没有死锁的点都能得到99+的分数。
基于度量的程序结构分析
1.代码规模
用较少的类实现了复杂的功能,但是代价就是Elevator类比较臃肿,足足有498行,差点就超出了最长代码限制。
2.代码耦合度分析
由于Elevator中的复杂功能,使得其代码耦合度远超其他的类,要降低耦合度的话,可以再建一个新类,用于存储Elevator的基础信息,用供给者模式和Schedule进行交互。
3.MUL图
这是第7次作业的MUL图,在第7次作业中,当来请求时 Input 将 Request 给到 Schedule ,Schedule 再将 Request 进行分配,优先分配RESET-Request , Schedule 把Request 分配到电梯井 (ElevatorShaft) 中,电梯井里有电梯,双轿厢电梯的电梯井中有两个电梯线程,单轿箱电梯中只有一个电梯线程,Schedule通过电梯井访问电梯的各种信息以用于分配Request,以及实现影子电梯策略。双轿厢电梯中的两部电梯的信息交互通过 ElevatorExchange 类实现。电梯的下一步策略在Strategy中实现,再通过Output输出。
架构设计体验
第一次迭代
第一次迭代中,每个请求的需要被分配的电梯是指定的,很容易猜到下一次的迭代中,请求肯定是需要我们自己去分配电梯的,因此在第一次作业中就考虑了请求分配的一个大类,写了一个调度器Schedule,同时将电梯的所有信息都设置成可修改的,以提高可拓展性。
聚焦于这次作业的重点,这次作业的线程关系比较简单,调度器线程向六个电梯线程中添加请求,基本不会出现什么死锁的情况。并且Schedule中也不需要任何调度策略,因此这次作业的难点就聚焦在电梯的运行策略。
通过翻看往届学长的博客,发现绝大部分人用的都是LOOK策略,因此我也使用了LOOK策略。同时在move中添加一点小小的优化,在楼层移动的过程中,当有新请求来临时,move请求可以反悔(这个反悔操作为我第三次迭代的bug埋下了伏笔)。
第二次迭代
第二次的迭代,难点有二。
第一:请求的分配策略;第二:RESET将电梯所有请求返回到Schedule的请求池中,在这个过程中很容易出现死锁问题。
难点一在我第一次迭代的基础上,用影子电梯策略很容易就解决了,并且有较高的性能,具体实现就是对现有的电梯进行深拷贝,但是不启动线程,然后整体用Schedule线程去模拟将该Request分配到六部电梯之后的运行,得到运行时间,取最小的,人最少的电梯,将Request分配给它。
难点二是我没有考虑到的情况,导致强测中的失利,这和我没有做足够的测试有关,快速完成代码之后心就放飞到清明假期了(没有用评测机对自己的代码做充分的测试,否则在中测之前应该能够发现死锁的问题。
第三次迭代
相较于往届,这次迭代难度远超之前,不仅需要考虑Schedule和Elevator之间的死锁关系,还要考虑ElevatorA和ElevatorB之间的死锁冲突问题。
为了防止Schedule线程干扰ElevatorA和ElevatorB之间的关系,新建了一个ElevatorShaft类,让Schedule线程只能通过和ELevatorShaft交互得到ELevator的信息,请求分配策略从影子电梯变成了影子电梯井,很好的解决了死锁问题。
ELevatorA和ElevatorB之间的信息交互通过ElevatorExchange类来实现,并且通过ElevatorExchange来解决双轿厢电梯在changeFloor的抢占谦让问题,但是由于第一次作业的move反悔操作,导致我这次作业也出现了Bug,被发配到了B房,在反悔时忘记取消对changeFloor的抢占,导致另一部电梯一直以为changeFloor有电梯存在,然后就一直等待,导致TLE。
新的迭代情景
可添加新电梯,设置奇偶电梯,奇电梯只能去奇数层,偶电梯只能去偶数层,这可以使得请求的分配需要考虑人员的路径规划以得到较高性能。
Bug分析
这一单元的主要Bug还是死锁问题,上述已经分析了我第二次和第二次迭代产生的Bug和解决办法。具体来说就是Schedule和Elevator线程之间的死锁关系,ElevatorA和ElevatorB之间的死锁问题。
解决办法就是使用生产者消费者模式,或者观察者模式,或者在中间添加一个信息交互类,防止线程之间互相访问导致的死锁问题。
在后续多线程编程的过程中,一定要先考虑好线程之间调度关系,再开始整理整体代码逻辑,提前防治死锁的发生。
优化分析
这一单元剔除死锁问题之后,主要优化在于电梯的运行策略和请求的分配策略。
电梯的运行策略采用LOOK策略。
请求分配策略采用影子电梯,在第三次迭代中使用影子电梯井策略,求得最快完成请求的电梯和响应的路径。
心得体会
对于多线程编程,一定要做充足的测试才能发现一些死锁的Bug,不能贪快导致对某些死锁Bug的忽略。
在后续多线程编程的过程中,一定要先考虑好线程之间调度关系,再开始整理整体代码逻辑,提前防治死锁的发生。
这个单元也有做的不错的地方,比如代码的可拓展性十分的好,很轻松的就写出了影子电梯策略。
未来方向
感觉可以降低策略调度的难度,让学生更加注重线程之间的关系,而不是把侧重点放在调度策略上,调度策略再好也不能比普通的调度策略提高多少分,反而使得很多同学忽略了线程之间的关系,导致这部分学生得到更低的分数,这是不太公平的一种做法,而且偏离了这一章的教学侧重点。