zoukankan      html  css  js  c++  java
  • 面向对象第二单元作业总结

    前言

      这个单元的作业质量总体评价来看的话比较差,都只是在通过中测之后就以为自己的程序没有线程安全问题了。自己本地测试也是使用了比较基础的测试样例,将模块功能逐个测试,没有考虑比较复杂的指令组合。导致强测的时候暴露出程序有许多考虑不周全的线程安全方面的问题,或者是没能妥善处理线程间共享数据的读写安全和及时更新。在听了老师单元总结中建议的架构后豁然开朗,以下就自己写的比较差的代码作为反面例子来分析其中比较危险错误的设计以及可以改进的方式。

    设计策略总结

      本单元作业的设计比较差的原因还是在于第一次作业的时候深陷课上讲的生产者消费者模式但是在考虑作业中电梯作为消费者的时候总是考虑上一条指令执行完后取下一条指令,这样就变成FCFS策略了,怎么也没有想出设计一个电梯的内部队列存储要运行的指令这个方法,最后在BUG修复阶段将电梯运行和调度器分隔开,共享电梯内部运行队列这个数据。有点像Worker-Thread模式,这样设计给我后来的作业奠定了一个比较好的方向基础。但是细节设计上上述共享的电梯内部队列数据没有进行有效的保护,这个问题在第二次作业中显现出来了,而且在程序运行过程中会出现一个线程调用另一个线程方法的设计,非常危险的设计。很容易导致线程间共享数据的读写不同步。第二次作业相比第一次只是增加了电梯数量,只需要在调度器中设计为电梯分配的算法就好了。第三次作业则将第二次作业写出来的带“锁”的可稍带多部电梯设计作为二级调度器,在这之上设计一级调度器,负责从输入线程中读指令,并且分析指令类型进行初步处理之后将指令交给二级调度器处理。

      我做的设计有点类似Worker-Thread模式,工人在做完工作后才会提交自己的状态给上级调度器。但是工人都是被动的获取指令执行,听从上级的分配,在听了老师的架构设计后发现工人主动将自己的状态提交给上级然后上级判断是否有工作交给工人这种设计会更加安全,共享数据由书写该共享数据的线程去提交给其余共享的读取线程会更加安全及时。相必自己BUG修复阶段一直无法修复的问题就存在在共享对象的处理上了。

    第三次作业架构设计总结

      我第三次作业的架构相比第二次作业多了一个总的调度器,负责读取指令并且对指令分类,对于新增的电梯指令交给相应类型的电梯调度器去处理,对于人员指令则判断是否需要拆分,拆分后将前半段指令送到相应类型的电梯调度器中,后半段存储到自己总的队列中,二级调度器(电梯调度器)负责将接收到的指令对自己负责的电梯列表进行判断是否可执行以及是否可稍带,如果都不行,那么存入内部队列等待电梯执行完毕后从中取出,依旧沿用了第二次作业的绝大部分设计,只是电梯线程新增了人员指令完成后向上级调度器报告执行完成的人员ID,从而在总的调度器中判断该人员是否存在,如果存在则将该拆分指令后半段插入相应的电梯调度器。功能设计上还是大体按照面向过程编程的思路设计,这样导致在最后共享对象加锁的时候容易将整个if-else语句块都括起来,性能大大下降。并且在向电梯线程中插入指令后,为了能够在紧接下来的判断中获取电梯更新后的状态,需要时常sleep一小段时间,很容易导致性能下降。

      从设计原则分析:

    • SRP原则:我自己在设计类和方法的时候力争每个模块只是负责单一职责,包括将调度器分为两级,电梯运行线程单独独立出来,都是为了各司其职。
    • OCP原则:这部分做的不是很好,整个程序在扩展设计的时候还是比较有难度的,需要对代码层面做稍微的修改,实现细节完全保存在了具体的类中,并没有抽象保存基本的设计。
    • LSP原则:抽象继承等并没有使用,包括电梯线程和二级调度器都是直接实例化类来实现的,只是参数不一致。
    • ISP原则:纵观下来,自己设计的代码确实存在调度器臃肿内聚,几乎核心的功能都保存在了其中,现在回头想的话起始可以将其中一部分功能抽象出来作为一个模块,使用接口实现定义,然后具体的类实现功能,这样既能体现对象的层次,也能减少交互,降低耦合性。
    • DIP原则:比较失败,模块之间没有层次化关系,只有数据交换的关系,直接从功能的角度去实现类,没有从宏观上抽象出解决问题需要的简单层次。

    第一次作业

     度量分析

    整体类图

    可以看出主线程只生成输入线程以及调度器线程,电梯线程是调度器运行的时候创造的。

    第二次作业

    度量分析

    整体类图

    和第一次作业没有比较大的区别。

    第三次作业

    度量分析

    整体类图

    相较第二次作业增加了总调度器,之后的调度器线程和电梯线程逐级创建。

      从上边的方法复杂度中可以看到,循环判断等都集中在调度器模块中,并没有实现比较均匀的分配,如果在设计之初或者实现的时候能够多考虑考虑设计上的臃肿毛病,那么很有可能就会避免一个模块中存在大量分支代码,既会方便单元测试也会比较好找到问题的所在。

    分析bug

      第一次作业由于是在bug修复阶段才想出来可稍带电梯的设计,时间紧迫就没能考虑完全锁的控制,导致最后存在一个怎恶魔也处理不了的bug,很可能是自己没有在电梯运行内部队列上加锁导致的。

      这个问题在第二次作业中显现的更为明显,中测没有通过,便尝试添加了无关锁,直接通过了,之后出现的bug在于我判断人数的时候出了差错,从电梯读取当前楼层和乘客数的时候没有考虑乘客集中进入电梯的时候状态更新不及时的问题,之后便采用了从内部队列判断整个运行过程中会出现的的最大人数判断的方法,一遍就过了。

      这次问题导致我设计第三次作业的时候格外小心,线程之间共享对象的时候要选择自己线程能够控制的,而不是其余线程自身的数据,最后产生的bug可能是无关锁导致的。第三次作业提交的时候采用的无锁版的第二次作业,显然第三次作业的中弱测是比较简单的,和我本地设计的简单模块化测试相似,没有产生进程冲突。导致强测的时候直接gg。老师在总结的时候提到过在已有设计满足基础功能的时候要多考虑线程间的共享数据安全,比如之前提到的工人模式中可以考虑有自主性的工人,这样的话就会避免其余线程调用本线程的属性导致的错误。互测也没有投入太多精力。

      这单元作业开头有点蠢,后续则一头蒙在其中进行设计,虽说在设计的开始阶段还是考虑了设计原则,但是在慢慢实现的时候因为功能渐渐抛弃了可扩展性。bug也是集中在核心的判断阶段,复用次数太多的代码在移植和迭代的过程中往往容易会在其中一小段的语句中蕴藏着比较大的坑。

    心得体会

      这个单元研究多线程编程,自己之前对于多线程编程一无所知,在第一次实验接触到多线程之后感觉锁的设计很迷惑,之后由于自己没有保存实验代码,只能去网上找锁相关的设计思路,都不太清楚,自己本地测试中也没发现锁的妙用之处,因此线程的运行多采用了while-sleep的模式,有指令就运行,没指令就睡眠,效率低下,性能堪忧。在听取同学们对于多线程编程的理解以及本次作业的设计思路之后,豁然开朗。果然在进行软件设计的时候还是需要先宏观设计执行层次,在从细节处保证线程安全和性能。之前的架构总结中,自己第三次作业的设计太侧重从功能角度出发,而不是从抽象层次设计。

      其次感受比较深刻的是线程的不确定性以及本地测试的难度大大增加。在第二次作业的bug修复阶段我分别对同一份代码提交多次会发现其在几个数据点存在过与不过的叠加状态,这非常令人费解,在一一对照输入与解释信息之后我才发现造成错误的原因在于电梯线程可能是在指令插入的时候正在进行楼层检查(对内部列表中的全部指令作当前楼层检查),这就可能会导致一个人员的起始楼层没能输出,只有到达楼层,这种错误即使在有锁的时候也会偶尔发生,对于指令的进入时机有特殊的要求,虽说发生的几率很低,但是毕竟是自己在设计的时候没有特别严谨的考虑到共享数据的安全。本地测试也是一大难题,我基本都是复制进标准输入然后测试,这样作用实在有限,而且局限性太大。自动化测试没有花时间去好好搞,现在强测只能吃哑巴亏。希望下单元作业能够掌握自动化测试,使自己本地的程序在测试过后更具有鲁棒性。

      

  • 相关阅读:
    【转】QT创建子对话框的方法
    IplImage转为Mat的方法
    浅谈Android选项卡(二)
    浅谈Android选项卡(一)
    Android来电、去电监听
    文件加密
    Java实现文件重命名
    使用单个httpclient实例请求数据。
    获取Android状态栏的高度
    [置顶] 微软翻译接口
  • 原文地址:https://www.cnblogs.com/xuwenguang/p/12711571.html
Copyright © 2011-2022 走看看