三次作业总结主要分为以下几个部分的内容:
- 多线程协同和控制
- 基于度量的程序结构分析
- 程序bug检验
- 心得体会
多线程协同和控制
第一次作业为傻瓜式先来先服务电梯调度,而且不考虑捎带优化。所以设计非常简单,只需要满足相关的线程同步避免轮询即可。
具体的设计为两个线程: (1) 电梯线程 (2)输入线程
线程互斥:两个线程竞争的资源区为 一个公共的队列,所以对该队列的访问需要进行加锁,
线程同步:当没有需求时,电梯线程等待;当输入添加到等待队列时,电梯线程被唤醒。
第一次作业为多线程的基本应用,为保证扩展性,不增加优化部分。
第二次作业为可稍带的先来先服务电梯调度。相比第一次只是增加了可稍带部分,所以只需要增加捎带算法即可。
同样的捎带算法是电梯每层停靠时自主检测完成的,所以融入到第一次作业的互斥区访问即可。
第三次作业增加到了三个电梯线程。
设计之初,不考虑电梯间的协作优化,只考虑可达性问题,将需要换乘的部分分为两段,分别给不同电梯调度,通过计算可达矩阵完成电梯分配。
每个需求到来时将其立即分配给某个电梯,则转化为第二次作业的部分。
每个电梯拥有一个自己的需求等待队列,独立调度。完成某项需求第一部分时,判断是否需要换乘,如需换乘,添加到另一个电梯的队列中。
所以这形成了一个哲学家就餐问题,再电梯换乘时,一个需求需要从一个队列删除再添加到另一个队列。
为了破除这种锁嵌套获取的问题,采取了缓存flush策略,即,将所有需要换乘的需求缓存下来,释放掉删除需求队列的锁后, 再获取添加需求队列的锁,将所有需求添加进去。
这样保证了任意线程再同一时刻只会有一把锁,不会产生死锁问题。
基于度量的程序结构分析
作业类图
第一二次作业类图是第三次作业类图的真子集所以,下面只给出第三次作业的类图进行分析。
从下图中可以看出,本单元类图结构十分清晰, transfor 类为计算转移矩阵的类,其被Input Model 和Elevator 调用。
InputModel 和 ElevatorObj 是主要的两个线程,分别为输入调度类和电梯类,两者被主类所创建。
InputModel 类中方法非常少,只有调度和输入,电梯把所有行为封装到自己内部,耦合性非常好。
综上,本单元类的设计采取了简洁高效的策略,类之间分工明确,结构简单。
UML 时序图
下图为UML 时许图,三次作业均采用了两线程模式,输入模块同时也是调度器,电梯换乘可以被视作新需求的到达来。
调度器将需求分配给某个电梯之后由电梯自行运转满足需求。
第一次作业
类的相关统计数据
LOCM表示耦合程度,期望其值低;FANIN表示内聚程度,所以期望FANIN的值要高。
方法度量
LOC表示代码行数,CC表示循环,PC表示方法的参数
可以看出大部分方法行数较短,几个主要的方法行数比较长,这是比较符合常规的。
第二次作业
类的相关统计数据
方法度量
第三次作业
类的相关数据统计
代码异味
通过分析看出在out 函数中使用了大量的神仙数(没有明确标明意义的常数),这个在以后的作业中需要修正。
以及比较复杂的条件和比较复杂的方法,这些都是需要进一步重构的方法。
方法度量
程序bug检验
对于程序中的bug检测,首先要立足于对多线程的深刻理解,在本三次作业中,虽然没有互测环节,但是我们还是尽量对自己的程序尽可能多的测试。
Bug检验是一个由简单到复杂的过程:1.分析线程关系-> 2.线程是否公共资源互斥-> 3.线程是否可以进行同步 ->4.捎带效率和可达性检查
脚本生成数据检查
- 使用python脚本,随机生成需求,并再随机的时间按批次投入。
- 按照电梯运行规则进行检查输出结果 和运行时间
检查出的错误主要有:
(1)线程synchronize 区域粒度不够细,出现带锁睡觉
(2)线程之间synchronize 嵌套,出现哲学家就餐问题
在测试中出现的其他问题,对于最后结束添加一个NULL进入队列,所以需要对NULL的需求特判
本地执行和采用命令行执行效果不一样,所以要经过命令行执行的检验。
bug 主要处于线程访问公共资源区的位置Elevator 的Transfor 函数区,所以对公共资源访问和电梯同步要仔细设计检查。
第一单元错误主要出现在顺序逻辑和某些输入WF情况下,对于多线程重点在于公共资源的互斥访问和线程的同步上。
总结
通过本单元的作业基本了解了面向对象多线程基础思想,进一步积累了一些重构的工程经验。
线程安全上讲,尽可能地不要让一个线程同时获取两把锁,这样极有可能造成线程死锁。
在线程同步方面,wait 和 notify 都是基于公共资源区的,这样从设计上可以避免死锁,同时也比较符合人的思维规范。
在设计代码的时候首先分析出公共资源区,然后对公共资源区的访问代码范围尽可能缩小粒度,避免出现重叠现象。
应该能够比较迅速的识别出经典的线程模式,如生产者消费者模型,哲学家就餐问题,发布订阅模式等,这些都有助于我们快速构建多线程代码。