OO第二单元小结

 

  依然是秉承着设计与实现分离的原则,第一部分主要是讲述这三次作业的在整体上和细节上面的思路。第二部分将考察自己对于第一部分设计实现的效果如何。第三部分是对bug和检测bug的分析,最后的第四部分是对这三次作业的一个小小的总结和感想。

一、设计思路及策略

  1.第五次作业

  电梯作业系列的第五次感觉是到目前为止历次作业中最简单的一次,甚至比第一次还要简单。只需要掌握了生产者-消费者模型,第五次作业就完全是个小的翻版,甚至都没有生产者-消费者模型复杂,因为缓冲区无上限,所以生产者都不需要阻塞,只需要无脑生产唤醒消费者就好。

  由于是傻瓜算法,所以我也没有做过多的设计。只需要两个线程,一个线程负责接受输入并加入到共享消息队列中,相当于生产者;另外一个线程将消息队列中的按先进先出的顺序处理即可。共享消息队列可以单独建一个类,也可以合并进消费者之中,但是需要注意所使用容器类是否是线程安全的,或者单独自己所写添加和删除方法的线程安全。

  2.第六次作业

  第六次作业相比较第五次作业而言,只多了一个要求:捎带。

  关于如何实现捎带功能,我其实有过许多不同的设想。我最初的几个想法都是需要实现一个调度器的,即输入线程将任务给到调度器,再由调度器根据电梯的状态进行任务的分配,这样既符合日常生活中电梯的管理,也方便了之后作业的实现。我的几个不同版本想法如下:

  (1)局部最优

  如果调度器中有任务,则每当电梯到达一层,或者执行完一个任务,就将自己的实时状态(运行方向,所在楼层,内部任务)传递给调度器,调度器根据电梯内所有的任务和当前的状态,对自己队列内的任务进行评估:分别计算分配这个任务与否电梯所需要行走的层数。

  这个想法的原理是基于局部最优化的想法,由于无法获取之后尚未到来的任务信息,所以时时刻刻对当前所有的任务进行规划,以达到对手头任务最优化的处理。

  这个想法的诞生是基于不转向算法对于一些特殊数据效果并不好:FROM-1-TO-7,当电梯运行到第三层的时候,假如又来了一个新的任务:FROM-3-TO-2,电梯是不会转向的,但是实际上转向先去执行第二个任务会比先执行原本的第一个任务快。而第二点,在数据较多,较混乱的时候,这种情况的出现应该是比较频繁的,所以那时这种算法对于这些任务的处理感觉上应该还不错。

  但是我并没有采用这个算法的原因也恰恰是因为在这个问题上的局部最优化,却有可能导致整体上的效率极低。我们不妨设想一下,假如电梯正在上行,这是接到了一个任务,通过计算,电梯转向。但是尚未开始执行新的任务的时候又获得了新的任务,导致电梯再次转向。也就是说,如果存在算法中权重较大的任务不断改变着电梯的方向,就会导致电梯一直在空驶,而无法真正地去执行任何一个任务,这会导致很大的时间开销。再考虑上述的第二点,如果存在大量这种需要频繁转向的数据,实际上数据再多,电梯也只需要从上到下再从下到上一趟就可以完成所有的任务,其相比较局部最优算法的开销最多也就是在结束所有输入后多走一圈。所以我也就并没有采用这种局部最优的想法了(实际上我当时也不会如何将消息从电梯传递到调度器,调度器的线程问题也没有解决)。

  (2)主任务调度

  就如同作业指导书中所写的,选择当前的一个主任务去执行,看看路上有没有同向、起点在路上的任务,如果有的话,就带上,再根据新任务的终点信息判断是否更换主任务。

  如果是这样的实现,那么主要的问题就在于,如何选择主任务。很多人是将电梯空闲时所获取到的第一个任务作为自己的主任务,但是实际上这样可能效果并不好,这也就引出了我对于主任务调度算法的改进,也就是我最终使用的版本:方向驱动而并非任务驱动。

  (3)方向驱动

  上述两种算法实际上都是派遣电梯去执行任务,换句话说,是任务驱动的。但是实际上,如果仔细考虑一下主任务调度算法,选择主任务其实也只是选择一个方向,因为主任务是随时可能因为新加入的任务而改变的,甚至是在去执行主任务的路上改变。

  鉴于上述思想,我放弃了电梯任务驱动的模式,简化了电梯的操作,电梯不用记载自己正在执行什么任务,只需要知道自己要去到哪就可以了。而分配任务的活就交给调度器来做。调度器告诉电梯,你需要去到哪里,我会把你路上能执行的任务都分配给你,你顺路把他们都执行了就好。调度器给电梯分配的任务,在电梯执行的过程中是不需要分为电梯内和电梯外两个队列的,电梯也不需要记载电梯内有哪些人。电梯需要做的,只是每到一层,判断下那个队列里面有没有起点或者终点在这一层的任务,如果有,输出相应的语句即可。

  既然是方向驱动,那么这个思路的核心,还是如何选择方向。其实对于任何一个位置,在任务已知的情况下,电梯总是存在着一个相对路径较短的方向。其可以被具体描述为:总是去往所有任务中,起点距离当前位置最远的两个反方向任务中距离当前距离较近的那个的起点的方向,说直白一点就是:向上的任务中最靠下的起点和向下的任务中最靠上的起点,谁距离当前位置更近,就朝着那个起点去走。这样的话结合了上面两者的思想,可以通过当前所有的任务决定电梯是上行好还是下行好。现在这个算法既有了局部最优的影子,也对主任务调度的算法进行了一点点改进。而事实上,强测成绩也说明了这个算法的效果其实还是很好的。

  3.第七次作业

  在第七次作业中,增加了电梯的速度和对可停靠楼层的限制,并且将电梯从一部增加到了三部。其实对于我而言,电梯速度的增加并没有影响到我的思考过程,因为根本无暇顾及。

  对于此次作业而言,在设计方面可以分为两个联动的方面,其一就是调度器如何根据任务需求将其进行拆分和分配,毕竟有些楼层不是所有电梯都能够到达的。其二就是单部电梯的运行算法,第六次作业最优的单部电梯调度算法在不同的调度方法中不一定就是最合适的。

  一个最为简单的想法,也是我最终的实现方法,就是单部电梯算法照搬,然后调度器的分配是遵从两个小的原则:能直达就直达,不能直达的尽早拆分。这种算法的好处是清楚,简洁,但是不足之处也一目了然,即拆分算法在一些数据下很可能效率极低。事实也如此,在强测的测试点中,大部分点的性能分还可以,但是有两个点没有在时间内跑完。也就是说这种算法比较吃数据,跑起来并不是很稳定。

  其实稍微好一点的调度算法一定是会实时地获取三部电梯的运行状态,再根据他们的运行状态进行任务分派的。单部电梯的算法也一定是和调度器进行配合才能达到效率比较好的效果。我有一个相对而言很初步的想法,即不管什么任务给到了调度器,不论是否直达,都需要根据当前几部电梯的状态看看是否要将这个任务拆分,也不一定是拆分成两部分。首先查看当前电梯有没有可以顺路把这个任务执行一部分的,剩下的部分和当前的部分都需要对于当前的电梯状态而言是执行起来不需要多花费时间的。然后将剩余的任务当做一个新的任务来看待。这样一来,一个任务不一定被拆成了多少个部分,虽然开关门次数会明显增加,但是可以保证每一个部分在执行的时候不会过多地浪费电梯的运行时间。

二、基于度量的实现效果分析

   1.第五次作业

  第五次的作业由于是傻瓜调度,所以完全照搬了生产者消费者模型,作为消费者的电梯只需要按顺序从共享消息队列中取所需要的元素即可。

  三个类,分别代表了输入线程,处理线程(电梯),和他们所共享的为了方便加锁而实现的消息队列RequestList。方法都很简单,和生产着消费者模型完全相同。

  三者的关系:

  耦合程度分析:

  代码比较简单,复杂度和耦合程度都不算太高,循环也比较正常。

  2.第六次作业

  第六次作业依然没有实现调度器线程,还是输入和电梯两个线程,也依旧是还有一个共享的消息队列。只不过这次电梯所需要完成的任务是由这个消息队列根据电梯的状态挑选之后给电梯的。电梯只负责运行。

  这次新建了一个enum类Direction,用来表明电梯的运行方向,从而完成电梯的运行。

  之间的关系与第五次基本完全相同,只不过每个类都调用了这个枚举类型。

  整个代码的耦合程度很高,可能和调用枚举类型有关系。另外,循环也多,主要是因为我电梯的run()方法就完全是一个根据电梯状态来走的一个循环。另外这次电梯内部的设计十分的差劲,颇有面条代码之风。

  3.第七次作业

  第七次作业相较于前两次而言,为了方便程序的结束,我最终还是加入了调度器线程作为输入和电梯的中间环节来给电梯分配任务。调度和电梯算法在第一部分都已经说清楚了,所以这里还是只列出自己的实现思路。

 

  让我很意外的是耦合程度,第一单元第三次作业和这个第七次作业的耦合程度都特别地高,整个程序写的也很病态。现在也不知道为什么,可能下去还需要再研究研究。

三、多线程处理、bug分析及测试思路

  这三次作业中只有第七次作业在强测中出现了bug,说是bug其实也还是算法的不完善,因为都是在限定时间内程序没有跑完所造成的。 

  关于多线程,我个人感觉最难的地方应该不是安全问题,而是各个线程之间完成任务的顺序以及如何结束的问题。昂神在水群里说过主线程应该作为最先开始和最后结束的线程,但是我的三次作业均不是如此。尤其是第三次作业,如何让程序结束也是困扰了很多同学的难题之一。

  如果只是希望线程安全,那么小心的加锁即可。但是我们更多的时候是希望提高程序的效率或者安排线程之间同步的顺序,由此可能会带来一些没有锁全,或者死锁之类的问题。

  测试方面,由于主要的时间都用来思考自己的程序了,也就没有在编写定时投放程序上面花心思,也就只能自己构造一些比较奇怪的数据多测试几次,然后肉眼比对其是否正确。

四、心得体会

  这次经历了学长们鬼哭狼嚎很久了的多电梯作业,感觉确实很难,尤其是最后一次,在如何调度的问题上思考了很长的时间,但是最后发现自己连架构都有点不知道怎么实现。多线程是第一次接触,写的程序还是太少,目前也只知道有关并发的很浅显的一些知识。

  这次比较新奇的经历大概就是第五次作业被查重了吧,和另外一个完全不认识的哥们被查重相似度57%。也只能说是有缘了,那么多人里面就我们两个写的程序很相似,思路不太一样,但是实现的思路很类似。希望以后这种事情还是尽量不要碰上吧。

 

 

posted @ 2019-04-24 18:33  ericliu0206  阅读(131)  评论(0编辑  收藏  举报