BUAA-OO-2024-第二单元总结

孙锐毅-22373500 学生 2024-04-20 14:25:39

目录

  • OO第二单元总结
  • 第一次作业
  • UML类图
  • UML协作图
  • 总设计
  • Main
  • Input
  • Schedule
  • Elevator
  • Decision
  • Request
  • Person
  • 同步块和锁
  • 调度器设计
  • Bug分析
  • 第二次作业
  • Uml类图
  • UML协作图
  • 新加入的设计
  • Reset
  • 修改的设计
  • Input
  • Schedule
  • Elevator
  • Myrequest
  • 同步块和锁
  • 调度器设计
  • 调度策略
  • Bug分析
  • 第三次作业
  • UML类图
  • UML协作图
  • 新增设计
  • SingleSchedule
  • DoubleCarElevator
  • Flag
  • DoubleCarReset
  • 更改设计
  • TotalSchedule
  • NormalElevator
  • NormalReset
  • Decision
  • 线程关系
  • 同步块和锁
  • 调度器设计
  • 调度策略
  • Bug分析
  • 总结
  • 稳定内容
  • 任务目标
  • 变化内容
  • 结束条件
  • 分配策略(调度器的变化)
  • 重置请求(电梯的变化)
  • 心得体会
  • 线程安全
  • 层次化设计
  • 碎碎念

OO第二单元总结

第一次作业

UML类图

img

UML协作图

img

总设计

7个类

Main

主要负责启动Input,Schedule,Elevator线程

Input

输入线程,读取请求输入,并将其加入到总请求队列waitrequest中

Schedule

调度器线程,从waitrequest中获取请求并分配到各个电梯中

Elevator

电梯线程,根据获得的decision执行每一步操作,完成运送乘客任务

Decision

根据电梯当前状态判断电梯下一步应该进行的操作

Request

请求类,多线程间的共享对象,其中的方法需要加上锁synchronized来保证线程安全

Person

乘客类,储存请求人员的信息

同步块和锁

在第一次作业中,我设置的线程类有Input,Schedule和Elevator三个类,这三个类的共享对象其实都是请求队列。因此,我将Request类里的方法进行了同步设置,都加上了锁来进行管理。

这样加锁之后,Input类和Schedule类对总请求队列waitrequest的管理是同步的,Schedule类和每个Elevator的局部队列也是同步的,从而保证了线程安全的问题

调度器设计

在第一次作业中,调度器发挥的作用其实并不大,因为每个乘客都指定了需要搭乘的电梯,Schedule类的作用就只用从总队列中取出乘客,再识别出其需要搭乘的电梯,最后加入到相关电梯的乘客队列中即可。

在这个过程中,Schedule线程与Input线程两者通过waitrequest总请求队列联系,Input线程充当生产者,Schedule线程充当消费者,当waitrequest队列为空时,Schedule线程等待,Input线程向waitrequest队列加入时,将Schedule线程唤醒。当Input线程结束时,会将waitrequest队列的结束标志置1,从而让Schedule线程读取到后进入结束阶段。

同时,Schedule线程与每个电梯线程通过电梯各自的request队列相联系,Schedule线程充当生产者,Elevator线程充当消费者,当request队列为空时,Elevator线程等待,Schedule线程向request队列加入时,将Elevator线程唤醒。当Schedule线程结束时,会将request队列的结束标志置1,从而让Elevator线程读取到后进入结束阶段。

Bug分析

第一次作业在最终的呈现中是没有出现Bug,但是在编写过程中,我仍遇到了一些问题。

第一个是关于多线程的理解问题,第一次遇到多线程,一开始还有些懵,但好在也是通过查找资料,学习等摸到了一些皮毛。

另外的一个是关于线程结束的问题。线程的开始十分简单,但是对线程结束,需要考虑什么时候结束,线程怎么判断应该结束的问题。第一次作业的任务中可以发现,Schedule类结束的标志是请求队列为空即可,Elevator类则是请求与电梯中的人均为空即可结束。对于线程判断结束,我是在Request类中设置了结束标志,让每个线程通过判断结束标志是否置1来帮助确定结束。

第二次作业

Uml类图

img

UML协作图

img

新加入的设计

Reset

存储Reset指令的相关信息,保障电梯Reset过程的进行

修改的设计

Input

加入对读入指令的判断,分别加入waitrequest总队列的不同类型的分队列

Schedule

加入关于Reset指令的判断与处理,将Reset之后产生的换乘请求加入回waitrequest总请求队列中

Elevator

加入关于Reset指令的判断与处理,能够改变电梯属性并完成Reset操作

Myrequest

在第一次作业的person类请求队列的基础上,加入reset类的请求队列,使得能够存储并输出reset请求

同步块和锁

在第二次作业中,我设置的线程类仍是Input,Schedule和Elevator三个类,这三个类的共享对象仍然都是请求队列。但是在第二次作业中新加入了对电梯的Reset请求,对于Reset请求的处理,除了原来的共享对象request队列外,电梯内的乘客people也需要同步控制,从而避免在Reset过程中出现乘客遗漏的情况。因此在第二次作业中在进行电梯Reset的处理时,我加上了关于电梯内部乘客people的锁,使在将电梯未处理请求与电梯内乘客的换乘请求统一加回waitrequest总请求队列的时候,不会出现同步问题,从而保证了线程安全。

调度器设计

在第二次作业中,取消了第一次作业中乘客指定电梯运送的条件,将由调度器决定将乘客请求分给某一步电梯进行运送。因此Schedule类就需要在第一次作业的基础上进行扩充,需要根据每名乘客的相关信息(如起始楼层,目标楼层),并结合当前每部电梯的情况(如当前所在楼层,运行方向,运行速度等等)进行分配请求的判断。

在这个过程中,Schedule线程与Input线程两者依旧通过waitrequest总请求队列联系,Input线程充当生产者,Schedule线程充当消费者,当waitrequest队列为空时,Schedule线程等待,Input线程向waitrequest队列加入时,将Schedule线程唤醒。当Input线程结束时,会将waitrequest队列的结束标志置1,但是Schedule类的结束在这次中不仅仅要看waitrequest队列是否为空,还需要考虑电梯Reset所可能产生的新的请求。

Schedule线程与每个电梯线程通过局部队列request联系。在第一次作业的基础上,加入了关于reset请求的处理。如果Schedule类要想Elevator类分配reset请求,则先将电梯中的reset标志位置1,再将reset请求加入电梯队列request中,让电梯执行reset操作,避免出现无法及时响应reset请求的情况。在电梯reset操作开始后,Schedule类还需要将电梯由于换乘产生的新的乘客请求加回waitrequest总请求队列。

调度策略

可能使用的调度策略有:

  1. random分配

    将乘客随机分配给各个电梯

  2. 均匀分配

    将乘客均匀分配给各个电梯(模6大法)

  3. 分配给接到乘客请求所需时间最少的电梯

    仅考虑接到乘客时间的最短“路径”方法

  4. 调参大法

    综合考虑各个因素(电梯接到乘客时间,电梯现请求数量,电梯人数,预计完成时间等等),并设置相关参数,即各个因素的权重,最后综合考虑分配。

  5. 影子电梯

    模拟电梯的运行,分配给花时间最少的电梯。即在输入线程获得一个请求时,深克隆电梯类进行模拟从而将请求分配各所花费时间最少的电梯;

在这次作业中未能实现影子电梯,没有能够体会到号称最强的调度策略。在实际体验中发现,均匀分配的总性能并不会太差,比random的性能更好。另外编写了“接待最短时间”与“调参大法”的策略代码,后来发现还并没有比均匀分配效果好多少,甚至会出现更差的情况。(也许该换回均匀分配 > _ <)

Bug分析

这次作业中出现了一个bug,是在程序输出“reset accept”和电梯输出“reset begin”之间输出的“arrive”指令数量超过了3条。导致这个Bug的原因是在程序接受到reset指令跟电梯进入reset状态之间的时间间隔太大,导致电梯在这段时间内做出了过多的移动操作。我在一开始电梯进入reset操作,是由decision判断电梯的resetrequest队列不为空就开始进行。在reset操作的一开始电梯自身将reset标志位置1,此时才算电梯进入reset状态。在接受到这个reset标志位置1后,Schedule类将不在为该电梯分配请求。因此,我将电梯进入reset状态的时间前移,在Schedule类为相关电梯分配reset请求时,就将改电梯的reset标志位置1,使电梯进入reset状态,电梯进入reset操作也变为判断reset标志位,使得电梯在收到reset请求后能够尽快进行reset操作,从而在程序输出“reset accept”和电梯输出“reset begin”之间电梯操作减少。

第三次作业

UML类图

img

UML协作图

img

新增设计

SingleSchedule

改为两层调度器模式,此为内层调度器,为所管理的电梯分配请求,完成从单轿厢到双轿厢的转换(结束单轿厢进程,开始双轿厢进程),完成双轿厢电梯的换乘

DoubleCarElevator

双轿厢电梯,与单轿厢电梯类似,改变了运行楼层,新增了换乘方法

Flag

标志类,用来管理双轿厢电梯的换乘操作

DoubleCarReset

用以存储双轿厢电梯重置请求的信息

更改设计

TotalSchedule

改为两层调度器模式,此为外层调度器,向内层调度器分配请求

NormalElevator

单轿厢电梯,即先前作业电梯

NormalReset

存储单轿厢电梯换乘请求,即先前作业请求

Decision

加入关于双轿厢电梯的运行判断

线程关系

在第三次作业中,我设置的线程类变为Input,TotalSchedule,SingleSchedule,NormalElevator,DoubleCarElevator五个类。

Input类和TotalSchedule类通过总请求队列waitrequest联系。TotalSchedule将waitrequest中的请求分配到SingleSchedule中,SingleSchedule根据当前状态(即为单轿厢电梯还是双轿厢电梯),向NormalElevator或者DoubleCarElevator分配请求。如果TotalSchedul向SingleSchedule分配的请求为双轿厢重置请求,则SingleSchedule会使NormalElevator进入结束阶段,并新开始两个DoubleCarElevator线程。

同步块和锁

这次新增的一个需要重点考虑同步问题的地方就是双轿厢电梯的换乘。因此我单独设置了Flag类,用以完成双轿厢电梯的同步控制。对换乘楼层设置occupied标志位,如果有电梯抵达换乘楼层,将标志位置1,离开换乘楼层,将标志位置0。电梯能否抵达换乘楼层要根据occupied标志位的状态来决定。FLag类中有更改occupied标志的方法,均被设置同步控制。

调度器设计

本次作业由于出现了双轿厢电梯,因此我将原先的单层调度器模式,改变为了双层调度器模式。外层调度器将请求分配给内层调度器,内层调度器再将请求分配给电梯,从而能够较好的处理双轿厢的问题。

调度策略

由于新增了双轿厢电梯,因此调度策略会发生不同的变化。当然random分配与均匀分配不会有太大改变,“接待最短时间”需要考虑是上轿厢还是下轿厢,“调参大法”需要更改参数设置。(影子电梯没有实现>_<)

Bug分析

本次作业出现了一个bug,是wait/notify问题。进过分析我发现,死锁的产生主要是发生在双轿厢电梯的换乘过程中。当两个电梯同时都要向换乘楼层前进时,先占据换乘楼层的电梯会进行换乘操作,另一个电梯会处于等待换乘的阶段。在进行换乘操作的电梯完成换乘后,会让出换乘楼层,并唤醒等待电梯。但是如果电梯抵达换乘楼层并不是进行换乘操作,只是接取换乘楼层的请求时,如果其将换乘楼层占领,另一个电梯如也想进入换乘楼层会进入等待,但是在换乘楼层的电梯在接取请求后不会将其唤醒,从而使等待电梯一直等待。因此需要在换乘楼层空出后,唤醒需要进入换乘楼层的电梯。

总结

稳定内容

任务目标

毫无疑问,每个作业的任务目标都是将所有的乘客送到目标楼层。其中一个核心内容就是乘客请求,这是我们需要处理的任务。在这三次作业中,除了第一次作业指定了运送电梯外(第一次作业的指定电梯更多是关于分配策略的变化),三次作业中乘客请求的主体内容是没有变化的,核心就是起始楼层与目标楼层,我们所需要完成的任务也都是将乘客从起始楼层运送到目标楼层。

变化内容

结束条件

第一次作业的线程结束条件很简单,就是看共享的请求队列是否为空

但是第二次作业加入了reset请求,使得结束条件的判断更为复杂,不仅仅需要考虑总请求队列,还要考虑尚未加入到总请求队列中的重置请求。

第三次作业加入了双轿厢电梯,带来的改变是换乘请求。如果换乘请求没有处理完,相应的电梯线程也不能进入结束阶段

分配策略(调度器的变化)

第一次作业指定了运送的电梯,因此分配策略无需考虑

但是在第二次作业中不在指定运送电梯,因此需要我们自己设计分配策略

第三次作业中又加入了双轿厢电梯,因此分配策略需要做出相应的变化。调度器也从单层变为了双层。

重置请求(电梯的变化)

第一次作业没有这个任务啊

第二次作业新增reset请求,带来了电梯属性的变化与重置请求的处理

第三次作业新增DoubleCarReset请求,带来了新的电梯种类,换乘请求的处理还有双轿厢电梯的运行策略

心得体会

线程安全

多线程程序最关键的一个问题就是线程安全,这也是导致我在编写程序中抓狂,最后程序出现问题的最主要原因。

在我看来,线程安全就是想要达到如果多个线程访问一个共享对象时,可以保证调用这个对象的行为都可以获得正确的结果。

为了达成这个目的,使用了两种方法,一种是synchronized关键字来对方法和代码块进行同步保护,另一种是使用了 wait()notifyAll() 方法来进行线程间的等待和唤醒。(也要注意使用顺序等问题避免死锁的产生)

层次化设计

在这三次作业中,层次化设计也十分的重要

一个是在类的设计上,通过Input类,Schedule类和Elevator类相互协作来完成任务。

另一个是在方法设计上。将分配策略相关的方法,重置处理的方法,电梯运行等方法都进行拆解。使得线程类的run方法中只保留最基本的操作,使得结构更加清晰

碎碎念

第二单元的作业相比第一单元来说,代码量有所下降,但是思维难度有所上升,主要是第一次接触多线程程序的编写,一开始没有能够理清楚各个线程的关系,包括该把哪些类设置为线程类,什么应该作为共享对象等等这些问题,但在对任务进行细致的分析后也还是能够逐渐的搞明白应该怎么去设计,也对多线程有了更加深刻的理解。

但是对于这单元的作业也还是有着一些想要达到却未能尝试的地方,一个是尝试影子电梯的分配策略,一个是在使用synchronized关键字外尝试使用ReentrantLockReentrantLock更加的灵活,在处理某些同步问题时可能更有优势。主要也是因为时间问题,加上担忧引入新的问题而难以解决,我没有能够做到这些实现,也是有点小小的遗憾。

OO课到这里已经快走完一半了,也是在“攀爬昆仑”的过程中学到了许多,也希望能够在接下来的课程中继续加油!

...全文
45 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
下载代码方式:https://pan.quark.cn/s/a4b39357ea24 全国计算机等级考试二级教程《Python语言程序设计》(2018年版)被视为针对Python入门者和备考人员的核心学习材料。该资料汇总了教材内的所有编程练习答案,这些答案在Python 3.5.3环境中经过实际执行测试,从而保障了代码的准确性和应用价值。备考人员借助这些答案,能够评估自身的学习进度,并深入理解和熟练掌握Python编程的基础原理与方法。 1. Python基础理论:Python作为一门高级编程语言,因其简明扼要的语法结构和卓越的功能表现而备受推崇。基础内容涵盖了变量、数据种类(例如整型、浮点型、字符串、布尔型、列表、元组、字典、集合)、逻辑控制(比如条件判断if-else,循环控制for、while)、函数的声明及使用、模块的引入等。 2. Python高级应用:Python的进阶内容涉及异常管理(try-except-finally结构),类与实例(面向对象编程的基础,包含类的构造、对象的生成、属性与方法的运用、继承机制、多态表现),装饰器(用于调整函数或类的功能特性),上下文管理器(借助with语句实现资源管理)等。 3. 数据文件处理:Python配备了全面的文件操作功能,涉及文件的开启、数据读写、关闭操作,以及多种操作模式(例如r模式用于读取,w模式用于写入,a模式用于追加内容等)。此外,还包括文本文件与二进制文件的转换处理,以及文件的位置调整、复制和删除等操作。 4. 标准库的运用:Python的标准库资源极为丰富,比如os模块提供操作系统接口,sys模块用于获取系统参数,random模块可用于生成随机数值,datetime模块负责处...
代码下载地址: https://pan.quark.cn/s/664854f01855 TCP/IP SOCKET协议构成了互联网通信的基石,它明确了网络设备之间数据交换的规范。TCP(Transmission Control Protocol)是一种以连接为导向、具备可靠性且基于字节流的传输层通信规范,而UDP(User Datagram Protocol)则是一种非连接型且不可靠的传输协议。在实施TCP/IP SOCKET的调试工作中,专业的软件能够协助开发者更清晰地洞察网络通信的流程,精准地找出故障点,并对代码进行改进。 "TCP模拟调试工具"即为一种实用的辅助性软件,它赋予用户模拟TCP连接、发送及接收数据的能力,从而对网络应用程序进行测试和调试。此工具或许具备以下几项功能: 1. **连接模拟**:可以模拟客户端与服务器之间TCP连接的完整过程,涵盖三次握手和四次挥手阶段,从而帮助理解TCP连接的完整生命周期。 2. **数据发送与接收**:支持自定义数据包的发送,并能显示接收到的数据,这对于核实数据传输的准确性极为关键。 3. **错误检测与重传**:TCP协议内含错误检测和丢失数据自动重传的机制,该工具或许能够模拟这些场景,助力开发者测试其应用的容错性能。 4. **流量控制与拥塞控制**:TCP通过滑动窗口机制实现流量控制与拥塞管理,工具或许提供查看和调整这些参数的选项。 5. **端口监听**:能够监听指定的端口,观察数据的进出情况,这对于服务器端程序的调试尤为有益。 6. **日志记录**:记录所有通信事件,便于后续的分析与问题定位。 7. **多线程支持**:针对处理多个并发连接的程序,工具或许提供多线程或并发会话的模拟。 8. **协议解...

301

社区成员

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

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