301
社区成员
发帖
与我相关
我的任务
分享同步块的设置:
最终,用于存储请求的 Queue 类一共延申了三种 Queue 类型,这些队列都会被两个以上的线程访问,处于临界区内,故涉及到队列内容修改时都必须加锁。而读取内容并不需要加锁。在函数声明处使用 synchronized 关键字修饰,即锁对象为 this 。
在电梯中有需要修改临界区内容的情况,我的Queue类并不暴露自身,从而保证所有修改都在synchronized修饰中,避免了并发处理的异常。
除了上述的用途,在双轿厢电梯中,对进入换乘楼层的动作使用两个轿厢都存有且相同的 refunds 队列(用于未完成请求的重新分配,且与其他电梯无关)作为锁对象。保证了同时只有一个轿厢可以进入临界区。
架构和调度器设计


三次作业的迭代以最小限度的修改作为基本思想。所以基本上没有大的改动。不过 dispatcher 类完全没有被更改过。调度器本身是一个线程。正如生产者-消费者模式里:生产者在队列满时不生产,消费者在队列空时不消费。即调度器线程和电梯线程通过队列进行交互,而不访问电梯本身。如在hw6中,调度器会根据电梯所在楼层和运行方向进行判断,将能够被捎带的乘客分配给该电梯,从而减少电量消耗。而电梯在运行过程中改变楼层、方向时会在队列中进行更新,以便调度器判断。这样可以减少调度器和电梯的耦合程度。
第一次电梯调度策略:指定电梯,调度器实际上只有分派任务。然而仍然实现了调度器线程,为下次作业迭代做准备。
第二次电梯调度策略:此次作业中,receive由调度器直接输出。如果有空闲电梯,可以直接分配。首先分派能够捎带的电梯,之后根据等待队列的长度进行平均分配。
reset 中的电梯并不能 receive 乘客。于是将reset的分派也交给了调度器:调度器将reset请求提交给电梯后,会将队列中 Valid 设 false。在计算电梯的函数中Valid为fasle的电梯将不会被分派。在电梯reset结束后,由电梯进程将队列Valid设为 true, 从而保证了 receive 输出的线程安全。否则如果在 input 直接分派 reset 请求,可能在 reset接收之后仍然同时输出了receive,导致错误。不过在hw7中还是把 receive 交给了电梯输出。
这里存在的一个问题就是如果电梯全部在 reset 中,那么调度器将无法分配请求,导致调度器轮询。这里我的解决方法是在所有电梯rest 时调用 Queue.class.wait() (实际上任何其他对象都可以)。而在所有电梯reset完成时都调用 Queue.class.notifyAll(), 从而在任一电梯 reset 完成时唤醒调度器线程。由于reset的限制:保证下次 reset 输入时上次 reset 已经完成。这样的 wait 并不会导致超时错误。虽然会有将所有乘客均分配给一个电梯的漏洞,然而互测的这样数据并没有超时。
第三次调度策略:reset 为双轿厢电梯过程中,我构建了电梯A,B和 双轿厢调度器三个线程。乘客请求先进入调度器,再分别分派给电梯AB。这样可以在不修改原来调度器的时候完成需求。然而双轿厢电梯对于“楼层”和“运行方向”这样的数据无法很好的抽象,使得原始的调度器策略效率低下。于是修改调度策略为模6,将 reset 请求放到 input 线程。而对于 receive 输出的线程安全,将receiv输出置于电梯线程中,保证了在电梯 reset 过程中将不输出 receive


性能
多个性能指标并没有进行仔细的分析,而是从朴素的要求入手:总运行时间短,尽快安排搭乘电梯、减少无用运行:抖动和空箱。在hw6的调度中通过捎带的判断尽可能增加电梯人数,可以很好的减少等待时间和电量。但是后面靠人数平均分配不是非常好的做法。hw7 中,模6的分配策略比较随机,在多种不同数据下表现都不算差。
碰撞策略: 禁止电梯在换乘楼层停留。对换乘楼层,采用 synchronized 同步块,两个轿厢用一个锁对象。则只能有一个电梯进入换乘楼层。
debug
多线程的debug稍显乏力。不能使用调试进行复现。不过通常可以使用System.out输出进行调试。一般情况下,并不会对输出结果也就是多线程运行造成很大影响。然而使用课程组提供输出包Timable输出则经常导致bug无法复现。除此之外,只能通过静态分析。在第一次提交前,warning 信息成功帮助我避免了一个bug。
对于同时具有reset、PersonQueue两个同步的线程, 由于使用不同的锁,wait 和 notify顺序将可能出错。例如在添加reset请求时,电梯可能处于personQueue 的wait 中,而不会被reset添加唤醒,导致等待时间过长。
心得体会
设计一个线程安全的程序绝非易事。即使在我最小修改的迭代下,还是出现了不少关于线程、输出顺序的bug。而对于迭代的设计也比较复杂。有时候突然觉得另一种写法更加合适,然而稍稍修改了代码之后,提交反而出现了错误。而bug修复可能仅仅是修改一下两行代码的顺序。就是这小小的错误在没有数据的情况下却很难发现。