前言

​ 第二单元 OO 作业的主题是多线程,课程组通过了电梯调度这个经典问题考察了多线程的调度。

​ 从第五次作业到第七次作业的迭代为,单部多线程可捎带电梯多部多线程可捎带调度电梯(电梯属性相同)多部多线程可捎带调度电梯(电梯属性不同,包含需换乘请求)

一、作业分析

作业分析将通过代码复杂度度量,UML 协作图,扩展性,优缺点等方面进行分析

第五次作业

​ 第五次作业的任务是完成单部可捎带电梯的调度。


设计分析:
  1. 第一次多线程作业中,我初步设想的是有三个线程,一个输入线程,一个调度线程,一个电梯线程,三者进行互动完成调度;

  2. 设计了BuildingFloorPerson三个类作为存储单位,帮助自己更好得管理输入请求和电梯楼层的人员状态;

  3. 采用”生产者-消费者“模型,其中Building类作为托盘,供输入线程传入信息,控制器线程获取信息(第五次作业因为单步电梯这种设计有一定的局限性)

代码复杂度分析:
  1. 很明显在Elevator类中方法的总循环复杂度偏高,是因为我把电梯的行为都设计在电梯之中,暂时没有想到很好的抽离出电梯行为的方法;
  2. ElevatorController两个类之间的耦合度过高,控制类因为需要调度电梯类,需要过多的电梯内部信息,这种交互模式不利于后续的扩展。
优缺点分析:

优点:

  1. 请求按照类来存储,在管理上十分便利,如果需要扩展也有着很高的扩展性;
  2. 采用了lock的锁的方式,个人感觉相较于synchronized更容易维护线程安全和修改,其次不关联的共享变量可以自己多创建几个锁,也方便自己的理解和使用。

缺点:这次作业的问题其实挺多的,刚刚接触多线程,对很多概念其实不太了解。

  1. 为了安全性,我把所有的方法都加上了锁,就看起来很累赘;
  2. 电梯和控制器的耦合度过高,不利于扩展和多电梯的调度;
  3. 电梯类的run方法里面就是一个停止的特判条件,容易产生 ctle,在互测的时候被hack了。
性能分析:

采用了 look 算法,思考角度是从生活的电梯实际运作情况出发,性能表现在预期之外;在研讨会的时候有同学分享了贪心的算法,在电梯人数没有限制的条件下确实是最优的方式。

第六次作业

第六次作业的任务是完成多部可捎带电梯的调度,这些电梯的属性都是一样的,可以到达所有楼层,其次有载客量等限制的出现。


设计分析:
  1. 与作业五相同部分不再赘述;
  2. 将三个线程控制改为两个线程控制,仅留下了输入线程和电梯线程;
  3. 控制器作为输入请求的分配器,来合理分配输入资源;
  4. 电梯类中存储专属的Building托盘,用于存储调度器分配的请求;
  5. 电梯类自行调度,从Building中获取资源,等输入结束后,由调度器通知所有电梯输入结束;
  6. 电梯类自行调度全部采用 look 算法;
  7. 添加了State的枚举类来帮助自己更好地管理调度器状态和电梯状态。
代码复杂度分析:
  1. 将电梯的调度从调度器移植到电梯内部,具有更好的可扩展性,但是相对应的电梯类的复杂度也持续攀升,之后如果还有需求,可以将电梯的行为再次抽象,降低复杂度。
优缺点分析:

优点:

  1. 设计逻辑更加清晰,更合理的状态管理和资源的分配;
  2. 控制器变成资源分配的中介,便于扩展和维护,虽然这样性能上可能会下降(但是实际却性能更好了,我也不知道为什么)。

缺点:

  1. 电梯类的方法可以进一步抽象,降低复杂度。
性能分析:

我采用的是单步电梯 look 算法,请求按照当前电梯请求数最少来进行分配,效果超出预期,可以说相当不错吧;与同学交流的时候,得知了另一种调度方式,就是请求资源的完全竞争,这样确实能够保证总的性能最佳,不过和他们差距也不大,觉得自己的设计可能在一定程度上更贴近生活。

第七次作业

第七次作业的任务是完成多部可捎带电梯的调度,这些电梯的属性是不同的,除了载客量和电梯运行的速度差异,在可停靠楼层上也有不同,有的请求需要换乘才可以到达。


设计分析:
  1. 与作业六相同部分不再赘述,作业六其实是个很好的框架,自己扩展第七次作业包括设计一共不到1小时;
  2. 因为电梯属性不同,添加了电梯工厂类帮助自己更好地创建电梯对象;
  3. 添加了安全输出工具类(因为课程组的要求);
  4. Person类中添加了是否换乘的属性,帮助自己进行请求的管理;
  5. 在控制器中添加了一个换乘人员数量的统计变量,用于防止电梯停止过早,换乘人员没有乘上电梯。
代码复杂度分析:
  1. 因为换乘的新要求的加入,电梯类需要对当前人员能否直达和是否需要换乘进行判断,电梯类再一次复杂度升高,因为最后一次了,自己也就没有花时间去进一步抽象了;
  2. 调度器类中,有个换乘和电梯选择的比对,所以复杂度也上升了。
优缺点分析:

优点:

  1. 电梯工厂的出现,帮助自己更好地完成了扩展;
  2. 在最初作业中的储存信息对象的创建帮助自己在最后一次扩展的时候节省了很多时间,只需要在相对应的Person或者Building类中添加相关属性,就能很好地完成扩展。

缺点:

  1. 类的复杂度太高。
性能分析:

因为电梯属性和换乘的新需求的加入,我对调度器请求分配的优先级进行了比较,他们的电梯完成请求时间近乎一致,但是在性能新标准人员等待时间上能够差近 30s,我选择了我测评机中最优的电梯优先级CAB,在换乘上我采用的是最近换乘点就放下的策略,但是这样的换乘策略不是最佳的,最佳的换乘策略应该是只有1 5 15(在我的架构下)。

分析小结

通过 SOLID 原则对代码进行分析

  • SRP:单一职责原则。我认为我这三次作业迭代下来,在这个方面在慢慢做好,从开始的控制器类和电梯类的高耦合,到最后分离耦合度,建立工厂,其实整体是在慢慢变好的,但是这个职责没有完成地很好,在电梯类中表现地很明显,电梯类除了自己的内部资源的维护,还需要兼顾调度,交互等多方面的工作。
  • OCP:开闭原则。这点没有实现得很好,每次新需求的添加,同时通过修改原来类的方法实现的,没有很好地用到继承等方式来降低电梯类复杂度。
  • LSP:里式替换原则。没有涉及继承和接口实现,没有体现本原则。
  • ISP:接口隔离原则。没有实现接口,没有体现此原则。
  • DIP:依赖倒置原则。没有很好地实现,过度地依赖实例。

​ 基本调度逻辑。

二、Bug与性能分析

Bug

自测:主要通过自动化测试以及极端数据测试

  1. 在第五次作业中,出现了死锁的现象,主要是因为notifyAll,之后将所有同步用lock的方式实现;
  2. 在第六次作业中,自己电梯的人数限制上除了问题,会出现超载的现象;
  3. 在第七次作业中,存在换乘人员还没换乘成功,其他电梯就停止的问题。

公测与互测

公测:没有出现bug

互测:第五次作业中被hack了一次 ctle,之后通过JProfiler检查出来确实有轮询的问题存在,之后就舍弃了这种设计框架

性能

​ 准确来说这三次作业的性能分都比自己的预期高很多,自己其实没有进行很特别的优化,尤其是在第六次作业以后,调度器作为请求分配的主体,电梯只服务其等待队列中的请求,而电梯本身都是按照 look 算法的方式进行调度的。

​ 我在调度策略上使用的是比较均衡的调度策略,按照每部电梯实时请求数量进行分配,在课下选择调度策略的时候,通过对多种调度器分配请求的算法进行了比较,选择了最为稳定和均衡的调度策略,最后跑出的结果确实超过自己的预期。

三、互测策略与测评机

互测

​ 互测方式主要是通过测评机跑和极端数据测试两种方式。

​ 测评机在这个单元用处除了自测的时候帮自己找 bug 外,在互测阶段只在第五次作业中帮自己 hack 到了人(搭测评机所花的时间比自己写 oo 作业的时间还多),可见能够通过强测并取得不错成绩的同学在正确性上都没有大问题。

​ 极端数据测试。这个是帮助自己 hack 到最多人的方式,启发来自第五次作业被人用超长时间间隔数据输入 hack 出 ctle。自己的自动化测试每次跑的请求数量在(35,45),时间间隔控制在 2s 以内,所以对于特定的 bug,如 RTLE 和 CTEL 都没有有效的 hack,只得自己构造特殊数据用测评机测试才能发现这些隐藏的 bug。

​ 在查看别人代码时发现,有人使用轮询,但是采用了sleep(1)这种方式来减少 cpu 的使用时间,然后我用了间隔近 60s 的两组输入来对这种 CTLE 进行 hack(超级加倍)。

测评机

​ 搭测评机是一种很好的尝试,自己两个单元的测试都是采用自动化测试的方式,第二单元主要通过自动化测试来选择好的调度策略,在 hack 上没有第一单元那么有效。

测评机的搭建步骤主要分为:

  1. 测试数据的生成(最简单的部分);

    • 主要通过 python 来自动生成
  2. 将测试数据导入到测试代码中;
    • 这次需要按照时间戳,定点爆破,使用了 python 中的 subprocess.Popen,将输入数据时间戳进行简单正则处理,再投放
  3. 将测试代码生成结果导入待检查文件中;
    • 使用命令行的文件拼接,检查的时候需要对输入和输出两者进行对比
  4. 检查输出结果。
    • 使用 java 生成 jar 包来进行检查,,可以通过命令行直接调用,设计十分面向对象,同时也能帮你很好理清楚可能出现的潜在问题(感谢wyk大佬在检查输出上的指导)

我一般通过 shell 脚本来编写自动化测试,因为调用逻辑就和命令行操作一样,再通过输入输出的重定向就能很好地完成评测。

​ 选择调度策略的时候自己写一个爬虫,爬自己每种调度策略跑相同输入的结果,然后进行简单平均值,加权排序,选出最优策略(同时我也用这种方法找到了屋中最强大佬的代码进行观摩,受益匪浅)。

四、多线程学习小结

  • 多线程的作业我认为首先是正确性,即线程的安全性与调度的合理性,其次才是调度策略上的优化等。
  • 有部分经验来自于近期的 OS 课程,关于怎样合理分配作业给 CPU,我第六次作业的最少任务电梯优先的策略就可以类比作业分配中的短作业优先,我的调度策略主要是注重任务分配的均衡方面
  • 多线程的框架在线程调度中有重要地位,例如这次的生产者-消费者模型,将共享资源抽象出来成为一个类,方便我们更为直观地进行代码编写和维护

五、心得体会

  • 我感觉面向对象的学习不像是学习任务的大头,反而在测试程序,优化上需要花费更多的心思,但是写面向代码和编写测试程序两者又是相辅相成的,尤其是在写检查程序的时候,会让自己关注到更多的细节和可以优化的地方
  • 在初始的时候为自己的代码留下扩展性,在代码的框架上有了点迭代的味道,难得的一次舒服的一单元的 OO 课程
  • 框架和性能是相辅相成的,好的框架的设计和有条理的调度逻辑能够保证你的性能不差,具体的优化只要落地到细节上,而不是大改框架去实现所谓的”优化“
  • 多点尝试,尝试不同的调度策略,尝试不同的换乘分配
  • 多看看设计模式,积极地应用或许有意想不到的效果

OO 第二单元的更多相关文章

  1. oo第二单元作业总结

    oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...

  2. OO第二单元优化博客

    OO第二单元优化博客 第五次作业没有性能分,但是,我在这一单元的宗旨就是写一个日常生活中 最常见的那种电梯,所以第五次我没有写傻瓜电梯,而是直接写了个\(look\),和第六次基本相同. 总计一下lo ...

  3. 【OO学习】OO第二单元作业总结

    OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...

  4. OO第二单元小结

    OO第二单元小结 一.三次作业代码分析. 1.第一次作业 第一次作业是单部电梯的傻瓜调度,由于其过分傻瓜,所以第一次作业我只有两个类,一个main,一个电梯,main类负责不断从输入流中读取命令,如果 ...

  5. OO第二单元多线程电梯总结

    OO第二单元多线程电梯总结 第一次作业 设计思路 Input为输入线程,负责不断读取请求并将读到的请求放入调度器中. Dispatcher为调度器,是Input线程和Elevator线程的共享对象,采 ...

  6. 电梯也能无为而治——oo第二单元作业总结

    oo第二单元作业总结 一.设计策略与质量分析 第一次作业 设计策略 在第一次作业之前,我首先确定了生产者--消费者模式的大体架构,即由输入线程(可与主线程合并)充当生产者,电梯线程充当消费者,二者不直 ...

  7. 2020北航OO第二单元总结

    2020北航OO第二单元总结 前言 本单元考察基于多线程的电梯调度问题,成功让我从一个多线程小白到了基本掌握了使用锁来控制线程安全的能力,收获颇多(充分体验了迷茫地de一个又一个死锁bug的痛苦). ...

  8. OO第二单元——多线程(电梯)

    OO第二单元--多线程(电梯) 综述 第二单元的三次联系作业都写电梯,要求逐步提高,对于多线程的掌握也进一步加深.本次作业全部都给出了输入输出文件,也就避免了正则表达式判断输入输出是否合法的问题. 第 ...

  9. OO第二单元作业总结【自我反思与审视】

    第二单元作业的完成史,就是一部心酸的血泪史…… 多线程的出现为我(们)打开一片广阔的天地,我也在这方天地摸爬滚打,不断成长!如果说第一单元之前还对Java语法有所了解的话,那么这单元学习多线程则完全是 ...

  10. OO第二单元(电梯)单元总结

    OO第一单元(求导)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉理解和掌握多线程的思想和方法.这个单元以电梯为主题,从一开始的最简单的单部傻瓜调度(FAFS)电梯到最后的多部 ...

随机推荐

  1. display: inline、block、inline-block、flex和inline-flex

    inline 共享一行 不能修改width.height属性,大小由内容撑开 padding属性 top.right.bottom.left设置都有效:margin属性只有left.right设置有效 ...

  2. js中this指向的问题与联系

    前言 JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下this的值是如何确定的.有js基础的同学面对这个问题基本可以想到:this的指向和函数调用的方式相关.这当 ...

  3. PyTorch 自定义数据集

    准备数据 准备 COCO128 数据集,其是 COCO train2017 前 128 个数据.按 YOLOv5 组织的目录: $ tree ~/datasets/coco128 -L 2 /home ...

  4. Innodb的存储及缓存

    参考[mysql技术内幕] 一.mysql体系结构和存储引擎 1.数据库与数据库实例 数据库:物理操作系统文件或者其他文件组成的集合: 数据库实例:有数据库后台进程/线程和一个共享内存区域组成. 数据 ...

  5. 算法 - 链表操作思想 && case

    算法 - 链表操作题目套路 前面这一篇文章主要讲链表操作时候的实操解决方式,本文从本质讲解链表操作的元信息,学完后,再也不怕链表操作题目了. 1.链表的基本操作 链表的基本操作无外乎插入,删除,遍历 ...

  6. 在Arch上使用Fcitx5

    目录 卸载Fcitx4 安装Fcitx5 配置 修改环境变量 系统登陆后默认启动Fcitx5输入法 配置主题 最终使用效果 参考文档 我是一个Arch+KDE的用户,所以下面的方法可能不适合所有的Li ...

  7. drf给上传图片重命名

    1.先在你项目中添加一个文件夹如:system 在文件夹下添加__init__.py 和storage.py文件,并在storage.py中添加如下代码: #复制代码 -- coding: UTF-8 ...

  8. [源码分析] 消息队列 Kombu 之 Consumer

    [源码分析] 消息队列 Kombu 之 Consumer 目录 [源码分析] 消息队列 Kombu 之 Consumer 0x00 摘要 0x01 综述功能 0x02 示例代码 0x03 定义 3.1 ...

  9. UI透明欺诈

    判断是否存在的代码:   private static boolean c(Activity paramActivity)   {     List localList = ((ActivityMan ...

  10. gtk---实现一个登录界面

    输入框 如果在GTK+中需要输入一个字符串,可以使用输入框,这是一个单行的输入构件,可以用于输入和显示正文内容. 输入框的基本操作函数 1.gtk_entry_new(void); 这是新建一个输入框 ...