Unit2总结-多线程

申剑璋-22373372 学生 2024-04-20 19:27:43

Unit2总结——多线程

引言

由于第一次接触多线程,在整个单元的作业迭代过程中遇到了许多困难。多线程不同于之前的单一线程,只需要考虑顺序执行的过程中是否会出现逻辑上的bug。多线程安全问题,死锁问题和CPU运行时间都需要在此次作业中考虑。

同步块与锁

由于多线程会同时访问一个共享对象,因此需要清楚所有的共享对象,以及如何对共享对象进行加锁来避免出现线程安全的问题。

  • 对于第一次作业,共享对象仅有zongqiuchi次作业,共享对象有总请求池waitingQueue和每个电梯所面向的请求队列processingQueue,因此要保证输入线程、调度线程访问waitingQueue和调度线程、电梯线程访问processingQueue时不出现竞争。故只需要使用sychronized对两个共享对象进行保护即可。
  • 对于第二次作业,增加了Reset指令。为了保证进程不会提前结束,因此需要记录请求中Reset指令的数量。本人专门创建了一个ResetCnt用于记录,则其为新增的共享对象。此外,若使用了影子电梯的调度策略,电梯线程和调度线程都会对电梯进行访问,因此电梯也是共享对象。对于新增共享对象的处理同第一次作业相同。
  • 对于第三次作业,没有新增的共享对象,处理方式与第二次作业相同。

调度器设计

调度器的功能是负责将总请求池中的请求分配给每一个电梯。用生产者-消费者模式来解释,对于总请求池,调度器是消费者;而对于每个电梯的请求队列,调度器是生产者。
还有一点需要注意,就是调度线程的结束条件。就第一次作业而言,只需要总请求池waitingQueue为空并且isEnd为真,则调度线程结束;但是第二、三次作业中增加了Reset指令,电梯会发生重置(生产请求放入总请求池中)。因此需要将调度线程的结束条件更改为;waitingQueue 为空 && waitingQueue isEnd && Reset指令全部结束

调度器策略

  • 第一次作业中,每个Request都已经指定好了分配的电梯。
  • 第二、三次作业采用了相同的调度策略,即影子电梯法。影子电梯的具体实现就是模拟电梯处理请求的过程,并且将每个电梯的处理时间记录下来进行比较。然后将Request分配给处理时间最短的电梯。对于影子电梯的实现有两点需要注意,在第二次作业中新增的Reset指令会对电梯进行重置。如果模拟过程中也出现Reset则会导致该影子电梯的处理时间与实际情况不符。因此,在对电梯运行状况进行克隆时移出了Reset指令,以此来保证影子电梯模拟的尽可能合理。而第三次作业新增了双轿厢电梯,影子电梯模拟其换乘过程十分复杂,因此采用了与第二次作业相同的模拟方式,但是对最后的处理时间进行了修正(加上了请求换乘所需的时间)。
  • 利用影子电梯实现调度策略的话,在模拟的过程中可以不仅仅只记录电梯的运行时间,还可以将电梯的耗电量等其他影响电梯性能的指标一并记录下来,然后利用调参法综合考虑多个因素的影响,让电梯的调度方式更加合理。

整体结构设计

第一次作业

本次作业需要创建三类不同的线程,包括:InputThread,Schedule,ElevatorThread。对于InputThread,该线程的作用是将所有输入的请求都存入一个总请求池(waitingQueue)。对于Schedule,该线程将waitingQueue中的请求分别调度给每一个电梯线程的请求队列。而ElevatorThread,则是处理对应电梯中请求队列的请求。在ElevatorThread中,有一个Strategy类用于处理电梯内部的运行策略,每次调用Strategy都会为电梯返回一个建议,决定电梯是移动还是开关门
由上可知,本次作业采用了生产者-消费者模式。对于总请求池,InputThread生产请求,Schedule消费请求;对于每个电梯的请求队列,Schedule是生产者,而ElevatorThread是消费者。

UML图

img

时序图

img

第二次作业

相较于第一次作业,Request分为了PeopleRequestReset两个子类。并且还新增了一个SImulateElevator的线程,用于模拟电梯的运行。ResetCnt类的加入则是为了方便处理调度线程的结束。

UML图

img

时序图

img

第三次作业

第三次作业新增了一类Reset指令,其能将电梯重置为双轿厢电梯。因此此次作业中将Reset分为了ResetFirstResetSecond两个子类,分别用来储存正常重置和双轿厢重置指令。并且还新增了DoubleElevator用于处理双轿厢电梯。由于ResetSecond会将电梯改造成双轿厢电梯,而双轿厢电梯相当于是两个有楼层限制的电梯在同时运行,因此借助ManageDoubleElevator将原有的线程销毁然后创建两个新的电梯线程,通过这种方式来实现双轿厢电梯。

UML图

img

时序图

img

如何避免双轿厢的碰撞

双轿厢的碰撞只会发生在换乘楼层,因此只需要注意两个轿厢在换乘楼层的行为。

  • 为了避免相撞,两个轿厢无法同时到达换乘楼层,因此需要为两个轿厢提供一个共享的锁。当其中一个轿厢想到换乘楼层时,需要检查该锁是否被获取。如果该锁没有被获取,则该轿厢可以到达换乘楼层并且获得锁;如果锁已经被获取,则该轿厢必须要等待,直到另一个轿厢离开换乘楼层将锁释放才能够获得锁并到达换乘楼层。
  • 同时若有轿厢内无乘客且停留在换乘楼层,那么该轿厢会自动离开换乘楼层,尽可能地避免发生碰撞。
  • 对于锁Flag的实现如下:

img

出现的Bug

  • 在第一次作业中,由于对sychronized关键词的理解不到位,对于共享对象没有进行有效的上锁,导致出现了线程安全的问题 。
  • 对于第二次作业,由于没有考虑Reset指令在接受后相应的电梯只能移动两层,导致强测时出现了严重的错误。最终采取优先分配和处理Reset请求来修复Bug。
  • 多线程修复Bug的最大问题就在于多线程代码的执行具有不确定性,有的线程安全问题由于执行顺序的不同而无法复现。在debug的过程中,为了能够查看对应时刻不同线程的状态采用了print方法,通过输出相应的对象内容推断出bug所在。

心得体会

通过这三次作业的迭代,我对线程安全和层次化设计有了更加深入的理解。在多线程代码中,资源的共享与竞争无处不在。因此我们需要清楚代码中所有的临界资源,并且在访问的过程中确保共享资源的正确性与一致性。如何正确且有效的使用锁,同样也是保证线程安全的一个重要因素。对于层次化设计,一个好的架构设计不仅能够便于代码本身的维护,同时也便于后续的迭代与功能开发。在本单元的作业中,我采用了生产者-消费模式进行设计。该设计方式有效的减轻了我的设计压力,也让我进行迭代时思路更加清晰。

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

301

社区成员

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

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