BUAA OO 第二单元

丁广晴-22371146 学生 2024-04-17 00:40:19

目录

 

结合线程协同的架构模式分析

架构设计的逐步变化和未来扩展能力画UML类图

画UML协作图

三次作业稳定的内容和易变的内容

同步块的设置和锁的选择,锁与同步块中处理语句之间的关系。

调度器设计,以及如何与程序中的线程进行交互

调度策略,如何适应多个性能指标

如何实现双轿厢的两个轿厢不碰撞的

出现过的bug以及debug方法

心得体会。


结合线程协同的架构模式分析

架构设计的逐步变化和未来扩展能力画UML类图

三次作业中,都是采用七个线程,也就是一个输入线程,6个电梯线程,不采用调度线程。具体的情况如下。

hw5

结构上,所有线程里都带有一个重要的共享对象(waitTable),负责交互信息。输入线程把来的人分给WaitTable(里面用hash套hash的方式来存人的请求,也储存其他需要共享的信息),电梯线程里通过这个对象拿人,放给自己的等待队列(RequestTable),然后在自己里运行。电梯各自运行的时候,通过Advice类,来获取建议,进行移动等操作。

由于总共只有一个共享对象WaitTable,而且没有调度线程,其实总体是安全不少。

hw6

由于电梯分配需要自己进行,所以必须知道各个电梯的情况来进行分配。

所以共享对象WaitTable里,不仅要有总等待人数,而且要有各个电梯的情况。因此建了Condition类,电梯各自内都有这个类,WaitTable内也有所有电梯的情况。

而如果把各个电梯的情况实时传递,就需要多加共享对象,带来死锁等可能,而且带来更多等待,效率也不一定好。所以创建了Condition类,存储电梯情况,里面只涉及简单的参数,如当前楼层,等待人数,电梯内的人数。每次电梯操作循环前,都把自己的Condition“克隆”一遍,再传给WaitTable,这样虽然Condition信息是需要都知道的,但是可以避免其成为共享对象。保证了共享对象的单一性,确保线程继续安全。这同时次把电梯自己的Condition传给共享对象的时候,也只需要一句,传递的信息也简单,减少锁时间。

Reset也作为一个请求,存在WaitTable里即可。

每个电梯循环一遍的时候,自己去找WaitTable要人,还有一个潜在的好处是,能去要人的电梯,一定是能动的,他要到人,自己一定可以跑,不会出现,所有电梯都在reset,我还想给电梯分人,但是没法分,出错。

所以主体架构还是,输入线程和六个电梯线程都通过WaitTable这一个共享对象来交流。没有死锁可能。

hw7

在这次架构里,由于电梯有了AB,所以我建了电梯A,电梯B,策略A,策略B,类,这些类有很多重复的,写的时候只要稍微改改即可(虽然虽然是那么多东西,其实只写一天,也能过中测)。

这里由于复杂度过高,不适合再沿用整个代码只有一个共享对象的方式(想的话,当然也可以),对于电梯AB之间共用原本的Condition,这个作为两者共用的共享对象。电梯分裂成两个时候,Condition内部发生变化,但是电梯AB共用同一个Condition,,也就是原本的电梯Condition下,把原本的电梯删去(当然,这个只在于电梯AB之间是共享对象,对于分人时候需要看情况的时候,还是把condition克隆后传给WaitTable)。电梯AB内也有重要的WaitTable共享对象,来传递信息。

主体架构就变成。  输入线程把信息都传给共享对象WaitTable 普通电梯,电梯AB,都通过WaitTable来拿人,并且把自己的Condition克隆给WaitTable来便于分人。同时AB内通过Condition来交流,避免相撞

三次架构中,由于都没有调度线程,分人是在拿人的时候解决的。电梯要去拿人的时候,就把没有分的人给分了,然后把属于自己的拿走(所以一个电梯可以给其他电梯分人,他来的时候,如果没有需要分的,直接拿),如果reset的电梯太多,那可以根据情况先不分完人,等他reset结束了,再来拿人。

未来拓展方面,由于结构相对不是那么复杂,以及线程目前还都比较安全,线程安全隐患很少,不会因为加上新的功能,导致原有线程安全被破坏,所以拓展能力还是不错的。

对于新加的情况,主要在电梯的Condition类里更改电梯的变化。对于分人策略改变,或者新的类似reset的指令,全局共享对象WaitTable作为一个核心的对象,也可以储存各种指令。

画UML协作图

hw7是迭代最终结果。最终的思路架构如上图。

 

三次作业稳定的内容和易变的内容

稳定的内容,线程数,主体架构。电梯自己的运行策略。

异变内容:电梯的Condition情况,分人策略,电梯的行为。

架构的稳定是很正常的,不然就要重构了。。电梯运行策略还是继续lock,各种之前写过的,其实主体都不变。异变内容,主要是由于拓展加的东西,主要涉及电梯情况以及分人策略等情况。电梯行为指的是reset指令的增加,多了分裂行为等。

同步块的设置和锁的选择,锁与同步块中处理语句之间的关系。

都采用自带的synchronized锁,自己写锁太麻烦,而且对于这次作业,不是最主要的效率影响。

同步块的设置,对于前两次作业,共享对象都是一个WaitTable,对这个对象的所有访问,更改情况,都进行锁,然后,对于电梯的wait和break判断的时候,由于需要知道WaitTable里的情况,所以如果可能wait的时候,需要对其锁住,也就是两个操作的时候,都需要设置同步块,避免刚判断WaitTable里没人,需要wait,然后WaitTable来人了,但电梯已经wait的情况。

同时,为了减少锁的次数,可以先向总等待队列取一次人数,如果此时电梯没人,再进入同步块,再取人,判断是否要wait和break,然后退出同步块。这样,只有我正常取不到人的时候,才需要判断是否要进入同步块,除非恰好这个间隙来人了,不然这个同步块内直接可以wait,不会每次循环都需要进入这个同步块处理,避免浪费同步时间。

第三次作业里多一个电梯AB之间的Condition共享对象,同样,对于其的更改,要用锁。

也就是,注意所有从共享对象来的信息,以及所有要写入共享对象的信息。避免刚拿来就被更改,导致错误。

两者关系,同步块中最好只放和这个锁有关的代码,避免锁的范围过大,影响效率。

 

调度器设计,以及如何与程序中的线程进行交互

调度器功能是由一个共享对象WaitTable承担的,三次作业都是。交互也很简单了,每个线程都有这个共享对象,调用其方法,就可以进行各个线程的交互。

各个电梯现场需要拿人的时候,自己调用这个共享对象的分配和拿人方法,即可实现调度功能。

相比于把调度功能单独放在一个线程里,从而导致输入线程和电梯线程都要与这个线程交互,并且带来很多 共享对象,避免大量的线程安全问题。这个方法把调度行为封装在共享对象WaitTable里,由电梯线程来实现,减少多个共享对象之间的问题。

 

调度策略,如何适应多个性能指标

hw5,采用lock,并且启用量子电梯。在传统的量子电梯的基础上进行一点更改,对于大于0.4秒可以直接的量子一层的,我就量子了,但是对于只能量子半层,只wait了不到0.4秒的情况,放弃把这点时间量子,从而让电梯慢一点,或许能带更多人。从而减少大量耗电量。当然,由于强测数据的不确定性,以及耗电量占比比想象的要大,根据越慢越快原则,或许直接放弃量子电梯会更好()

hw6,关于分配给哪个电梯问题,根据几个参数,分配权重。以所有电梯当前的人数为基础,选择人最少的电梯。并在次基础上,把电梯的性能(速度和容量)转换为人数指标,(比如,容量8人和4人的电梯,分别看做,这个电梯多了0人和4人),以及能否捎带(能捎带的,默认这个电梯的人数更少6个,之类的),来进行加权。最后按照最终的数值,来选取最适合放入人的电梯,同时,由于电量的重要,曾想过,除非人太多,不然让一个电梯根本不动 ,来减少耗电量(暴论),在人太多时候,并且有reset的电梯,就先不分人。。缺点是,采用加权相加的方法计算要放入哪个电梯的话,由于我没有类似强测的数据,没法得知,各个部分占多少权重,能捎带对应的人数能算作少几个人,这样。

hw7,同样在权重的基础上,双轿厢给予一定的优先级(看做少好几个人),来减少耗电量。

如何实现双轿厢的两个轿厢不碰撞的

电梯AB之间有一个共享对象Condition中可以存当前AB所在楼层,每次arrive,中sleep了0.4秒后,如果自己要到的不是换层楼层,那就到就行,不可能装上,如果是换层楼层,就拿取Condition的锁,看对方是否在换层,不是的话,就把自己的情况设置成换层楼层,并存入Condition,是的话,那就当做自己啥也没做到,然后放出锁。,啥也没做到就重新再来一次循环,也就是再会sleep0.4秒一次,等待对方退出换层楼层。拿锁机制保证,同一时刻只能有一个在换层楼层。即使两个都想进入,也会又先后顺序。

并未每次电梯到换层楼层开关门拿完人,就得退出换层楼层,保证另一个电梯一定可以进入换层楼层

出现过的bug以及debug方法

hw5的bug是,没有保护完全。 在 if(a)语句A  else if(b) 语句B 的情况中,只对语句A和B进行保护,但是其中else if(b)那一行之中,也可能出现共享对象被更改,所以整个要都放放在synchronized语句块里。。。debug方法,是肉眼观察自己的代码,(其实很快,bug经常是这么d的,而且,对于这种上千次复现一次的bug,想通过其他方法去dbug,也很困难,基本难以做到在bug复现过程中来找bug。不如理清自己思路,让自己跟着数据行走的方向,去看代码去找bug,效率其实很高,每次dbug的速度都还不错())

hw6是因为情况没有更新及时,我在设置了,如果人数最少的电梯人数超过20,并且还有reset的电梯,那就先不分人,但是由于分所有人是一瞬间的事,而我是在每次把所有人都分完后(也就是分人方法最后要结束的时候),再更新各个电梯的人数的。导致分人时候,每次分完一个人,电梯的人数没有及时变化,所以一下来大量人,我会都分给人数最少的那个电梯。导致超时。

这个bug也很好d ,甚至只是跑的慢,都不一定算什么bug(),目标很容易锁定在哪几行,直接改一下即可。

hw7没bug,刀的很爽(bushi)

心得体会。

线程安全方面:好的架构,或者说,更安全的架构与设计方案,可以减少大量的线程安全问题,甚至基本上不会有任何死锁危险。比如,对于实时性不是很强的东西,有延后是允许的话,没必要作为共享对象,clone过去即可。减少共享对象的类型,与语句块的复杂情况,可以很好的保证线程安全。

层次化设计:层次化设计后,可以更好的细分责任,减轻思路上的压力,只要明确各个部分的任务,就可以着手去写,同时,对于查找bug困难的情况,可以相对更快速锁定,减少dbug的时间。

其他心得总结:

bug方面。写的快,没好事。相比没出bug的第一单元,第二单元hw5和hw7,都是一天过中测,hw6,也挺快,结果就是,hw5和hw6互测都被刀了。(虽然也可能是任务变难,但是有的bug确实是很简单的原因,写的快了,没注意)

架构方面。虽然相比第一单元经历过重构,这次更难的单元,竟然没有重构。但在hw7的时候,也感觉自己迭代的有点屎山(或者说好听点,一点也不优美)。

毕竟一个电梯突然变成两个,不太确定该用什么方式来放这两个电梯,是在原本的电梯线程下,原本的电梯作为小的调度线程,还是把原本的电梯删了,电梯AB直接挂在输入线程下呢(我没有调度线程,所以直接挂在输入下,或者说中间用共享对象连接),最后觉得后者适合我的架构,不过在权衡过程中,还是感觉,这个拓展的,不是很好。我的架构虽然可以完成这个迭代,但确实毫无这个迭代方向的准备。所幸最后写完,感觉架构还算安全。

当然更主要的是,虽然我觉得我的代码架构比较奇怪,但是互测中,看过别人的代码,感觉也有点迷,给了我一些信心(bushi)。毕竟是第一次写多线程,能做到迭代成功,并且思路清晰,就不错了。哪怕架构可能不是最主流的调度器架构,但能理清自己代码的思路,对多线程的代码,有自己的处理方式,也算有所收获。

...全文
74 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧