BUAA_OO_HOMEWORK_UNIT2_总结与反思

第一次作业

设计策略分析

在阅读过题目之后,我们可以发现,这个题目其实就相当于外部不断的给楼内输送人,然后把楼当做一个中介,电梯不停地去每一层楼层取人然后完成人的要求即可。

所以我在此处用了一个稍微有些变形的生产消费者模型,把输入作为生产者,电梯作为消费者,然后楼作为一个托盘,一旦出现输入,就把输入转化为人放进托盘里,然后消费者从托盘取物的时候要先进行一个有没有可取物品的判断,也就是说电梯运行到某一楼层时要查看托盘中有没有处在这一层的人,如果有的话才把人从楼中取走。那么我们可以发现可能出现数据冲突的主要就在于托盘中的判断是否可取,取物品,放物品这三个方法中,所以这三个方法都要锁起来。

由于本次作业中我还不太熟悉多线程的编程,所以我的主要精力都放在了线程安全的保持,而没有钻研电梯的调度策略,只是复现了我在日常生活中观察到的大多数电梯采取的调度策略,首先取走最早来到楼中当前在楼中等待的人,然后在过程中捎带所有与此请求运行方向一致的人,有人能上就上,有人能下就下。

我在此次作业中测试点全部通过的情况下获得了87.52分,可以发现性能分还是比较低的,思考之后觉得还有以下几点可以优化。首先,如果目前主请求在10楼,而电梯在1楼,那么电梯在去接主请求的过程中是可以捎带一个从2楼出发去9楼的人的,而我的程序无法实现这点。其次,因为这次作业不必考虑乘客的感受,可以选择每次的主请求选取最近的一个请求,这样可以节省一些来回折返的时间。另外,也可以考虑不必一直按照主请求的方向运行,对一些比较近但是相反方向的请求放行,或许可以节省一些开关门的时间。

程序结构分析

在本次作业中,我一共设置了四个类,分别是电梯类,输入类,托盘(管理者)类,和主类。类图如下。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第1张图片

可以注意到其中比较复杂的类是电梯类,因为我的设计中电梯类run内部进行着自我调度,同时为了方便后续扩展和迭代,我将电梯操作划分的较为细致,上行,下行,开门,关门都有自己的方法。同时从图中可以看出,生产者与消费者(也即输入和电梯)间没有直接的联系,都是都通过了管理者类进行数据的放置与获取,此时保证管理者对象的安全就是保证了程序中大部分的数据安全。

另外,作业中方法的度量分析如下。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第2张图片

可以看出openDoor和run方法较为复杂,这是因为我在开门的方法中还进行了人员上下控制,此处应该进行解耦,否则可能造成此后的修改困难,而run方法则是因为我在run中完成了整个电梯的调度算法,所以显得较为臃肿。

程序bug分析

此次作业我在公测,强测和互测环节均没有被人发现bug,也没有发现别人的bug,在自我测试的阶段曾经出现过线程无法结束的bug,最后的解决方案是如果输入已经完全结束则通知管理者类,在管理者类中保存一个属性为totalempty表示托盘中不会在放入新的请求,同时通知所有线程,那么在电梯线程中判断,如果当前电梯已经不再运行,同时托盘已经为空并且不会再放入新的请求之后就终止线程。

心得体会

本周作业我从一开始对多线程编程一窍不通到可以编写出一个bug较少的程序,离不开书籍和自学,但是此时对线程安全的认识还较为浅薄,就是把所有可能产生问题的代码段全部锁住,其中包括了一些没有必要上锁的片段,导致了性能的降低,所以此后还应该多多学习线程相关的知识,继续增进自己的多线程编程能力。

第二次作业

设计策略分析

本次作业的要求在第一次作业的要求上新增了两个主要的变化,第一个变化是从调度一个电梯变成了调度三个电梯,同时电梯新增了一个容量的限制,其余的部分变化都不算太大。

所以此时我们就需要来思考这两个新增的要求会对我们的程序设计产生怎样的影响,先考虑容量限制的问题,我们可以在要从托盘中取请求之前判断一下当前电梯里的人是否已经满员,如果已经满员就不取,这个问题就简单的解决了。同时我在此次作业中仍然采取了与上一次作业中类似的电梯调度策略,所以就要考虑到一个问题,在主请求没有上电梯之前的判断就要先加一个空气人占位置,防止因为在主请求上电梯前就已经因为捎带已经满员的尴尬状况。

然后我们再来思考第一个新增的变化,此时楼中有3个电梯了,从线程安全角度来看,我们产生了一些需要锁住的原子操作。比如曾经我们从托盘中取人的操作是先看托盘中有没有可以取走的,然后如果有可以取走的就取走,此时因为有三个消费者同时从托盘中取请求,如果不把这两个操作锁住,让他们变为一个原子操作,就可能出现我先判断为当前托盘中有可取请求,然后这个请求被其他消费者取走了,那么此时这个消费者就取到一个实际上不应该被取走的请求。所以首先应该将这个判断->取走这个过程变为原子操作。那么有了这个意识,我们就可以发现还有一个地方应该变为原子操作,就是判断托盘是否为空和从托盘上取走主请求。这样,三个电梯时的线程安全问题就解决了。但是此时还有问题没有解决,就是这三个电梯采取怎样的策略决定载哪些人?在这里我仍旧沿袭上次作业的思路,考虑生活中大多数商场电梯的做法,让电梯选人,三台电梯同时开动,一旦有请求进入就去拿请求,谁拿到请求就由谁完成请求。

程序结构分析

在此次的程序中,我沿袭了上一次作业中的结构,仍然是使用了四个类,类图如下,可以看到和第一次的类图基本没有太大的差别。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第3张图片

另外,本次作业中各种方法的度量分析如下,比较复杂的仍然是电梯的run方法和opendoor方法,问题和上一次作业中一样,我在opendoor中仍然采取了较为复杂的判断上下人方法。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第4张图片

程序bug分析

我在此次作业中公测,强测和互测阶段都没有被发现bug,在强测中取得了98.53分,但是在公测前的自行测试中曾经发现过一个re错误,在某些运行中从托盘取请求可能取不到,这个错误的引发原因就是没有将判断托盘是否为空和从托盘上取请求作为一个原子操作,导致之前判断托盘不为空,但是托盘上的物品被其他进程取走后为空了,此进程还在试图从托盘上取物。

心得体会

这是我开始OO课程后第一次能复用自己上一次作业的代码(抹泪),将各类操作划分的较为细致之后修改的易度提升了,同时也有了较好的扩展性,此后的作业还是不要偷懒,认真解耦,把操作划分到合理的大小才能保证代码迭代时的效率。

第三次作业

设计策略分析

在此次作业中,要求比上次新增的主要集中在两个部分,第一个部分是这次的作业可以新增电梯,第二个部分是此次的电梯有不可以停靠的楼层。

首先我们来考虑第二个问题,新增一个不可停靠的楼层首先意味着电梯在开门和取人前要注意这一层能否停靠,其次意味着有一些需求的乘客可能需要在过程中进行换乘。于是我先写了个小程序判断了一下每个请求是否都能由两部以内的电梯完成,然后发现是可以的。于是我们就可以考虑把那些无法由一台电梯运送全程的乘客拆成上下两半。这样我们就保证了每个请求都可以直接由一部电梯完成,同时我们可以发现如果每次取请求的时候都判断这个乘客能不能上能不能下太过麻烦,所以我们可以直接考虑开三个托盘,三个托盘分别放着A类可以完成的请求,B类可以完成的请求,C类可以完成的请求。另外还有一个问题,仅仅把乘客拆分成上下两半无法保证电梯一定是先完成上半个请求再完成下半个请求,所以我们考虑新增一个类似预备托盘的东西,先把所有下半请求放在预备托盘中,然后一旦上半请求被完成了之后,就从预备托盘中把下班请求取出,放到真正的托盘之中然后通知电梯。

而第一个问题只需要完成一个新增电梯的操作即可。调度策略和上一次作业类似,但是对于一个可以同时由多种电梯完成的请求,我会随机的把它放在一个可以完成它请求的电梯类的托盘上。

另外,为了保证电梯的效率,所有需要换成的请求,我都先计算了一个浪费最少的换乘楼层,然后用这个浪费最少的换乘楼层分割上下两个请求。

最后,这次线程安全问题和第二次作业中类似,唯一需要注意的就是此次判断进程结束的条件除了没有新的输入,托盘已空还有预备托盘已空,而且这三个判断应该为一个原子操作,因为有可能先判断托盘已空,然后其他进程把一个预备托盘中的请求放入到了托盘中,同时此时预备托盘已空,然后本进程再去判断预备托盘,发现都是空的,就结束了进程,但是实际上此时不应该结束进程,从而导致错误。

可扩展性分析

此程序的性能应该较为平衡,但是可扩展性较差,考虑新的扩展可能涉及到乘客请求的变化,此程序的做法是基于乘客进入楼内后请求不会再进行变化进行的设计,所以需要考虑优化这方面的问题。

程序结构分析

在本次作业中,我一共设置了三个类,分别是电梯类,托盘(管理者)类,和主类。类图如下。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第5张图片

此次因为考虑到既有请求的输入也有新增电梯的输入,所以我直接将输入类和主类合并到了一起,直接从主类向托盘中放置请求。

另外,作业中方法的度量分析如下。
BUAA_OO_HOMEWORK_UNIT2_总结与反思_第6张图片

可以看出除了openDoor和run之外,canget和get方法也有一些复杂,这是因为我将三个托盘都放置在了一个管理器中的结果,如果还可以进行修改,可以考虑直接设置三个管理对象,每个管理对象只放置一个托盘,新建电梯时新建哪个类的电梯就传入哪个类的管理对象。

程序bug分析

在此次的公测中曾经出现过新增电梯开门过快的情况,经检查是我的时间戳初始化位置不对,应该在一开始就初始化时间戳。而在强测和互测中都没有出现bug。

心得体会

此次作业的代码复用率也比较高,但是一开始的时候没有意识到不能简单的把一个人劈成两半,造成了一点时间上的浪费,以后写作业之前还是要严谨的多考虑一点可能性再进行编写。经过这个单元的学习我感觉我已经可以算的上是初步入门了多线程编程,但是因为作业一直使用的是消费生产模型,过于单一。所以我认为我还应该对其他的模型进行一个学习。

你可能感兴趣的:(BUAA_OO_HOMEWORK_UNIT2_总结与反思)