301
社区成员
发帖
与我相关
我的任务
分享

基本架构:
输入线程将输入加入等待队列
调度线程在等待队列中取出请求加入选择的电梯,在等待队列没有请求但是双轿厢电梯仍可能放出新请求时等待(其实是不断sleep200毫秒)。对于乘客请求,选择电梯的方法基于电梯类中编写的一个代价函数。
电梯在自身请求池中请求仍未处理完时工作,在暂时没有请求时等待,电梯自身运行采用look算法
使用synchronized设置同步块,防止数据冲突的问题(~~虽然最后还是在这个地方出线程安全问题了TAT~~)
锁都是加在多个线程都会访问的数据上,例如输入线程和调度线程都会访问wait这个Waitqueue对象(而且后面电梯线程也会访问并写),因此在写Waitqueue这个类时,类中的所有方法都加上了锁;
还有在电梯的请求池,也就是Perreqpool类的一个实例allreq,也在所有方法加上了锁,因为调度线程可能会访问allreq,例如调用allreq的addreq方法向电梯请求池中加入请求。
主要的锁都在以上这些部分了,事实上在我的代码中还有很多需要加锁的东西(也就是电梯里的一堆Arraylist),但是当时写作业的时候没注意到这些Arraylist会引发线程安全问题,~~而且自己懒~~,导致自己最后不仅两次互测都被hack,而且hw7强测挂了一个点,还是使用线程安全的CopyOnWriteArrayList好啊。
就是基本的一直尝试从等待队列拿一个请求,如果是reset就直接丢给对应电梯;如果是正常请求就遍历六部电梯调用cost方法非常非常简单的计算接这个人的代价(但是面对强测的效果却出奇的好,搞得我都怀疑是强测数据的问题了),把这个请求分配给代价最低的电梯。
接下来就简单介绍一下这个简单的代价函数:
考虑的方面主要有:电梯目前接的请求总和(包括在电梯里的和没在电梯里的,还有现在正再考虑的请求也算一个)与电梯最大载客量的差值,也就是**空间代价**,若为正,超过人数 * 10000,这是最重要的参数,优先级应比电梯一次接到并送达待考虑请求的时间要高;电梯一次接到并送达待考虑请求的时间(以毫秒为单位),在这个参数中,如果是可捎带请求,时间代价为0(事实上我觉得这个设计挺烂的,但是最后可能是没它什么事所以没多大影响),以及电梯方向、接乘客方向、乘客自己的方向如果有不相同的按照最坏时间(来回一次再接或者送)加到时间代价上。
最后时间代价和空间代价之和即为总代价(虽然代码里写了考虑reset时间但是后来发现根本没有用,最后写的是c = csize + ctime 而不是 c += csize + ctime,笑死)
和普通电梯的唯一不同是考虑是否经过换乘楼层,如果不经过,时间代价减半(因为另一部电梯还能跑)
代价函数总结:没啥技术含量和代码量的设计,利用电梯目前有限的信息尝试给出指导罢了。但是需要注意的是不能先入为主,因为电梯目前知道的是有限的,不是影子电梯那样有最后的反馈,因此在计算时间代价时**不能自以为是的认为一次来回总能接到这个人,只能在电梯不可能超过限载人数使这样认为**,所以就应该优先确保这一点;另外就是双轿厢电梯的优先之处要在代价函数中有所体现(简单的时间代价减半就够了)
很难有我这么逆天的设计会在一个线程里跑两部电梯了,我把双轿厢电梯类写在电梯类里面,然后如果是双轿厢电梯就在电梯线程跑双轿厢电梯的方法(实在是赶时间之作,虽然以我的脑子也想不出多好的设计,不过确实逆天)。
在这种情况下,双轿厢电梯的move方法在改变电梯楼层是会判断是否于另一个电梯的楼层相同,这样就完事了(至于move的sleep只在至少一个电梯真的移动了才会进行)。~~虽然move同步了但是开关门没同步~~
我对线程安全的体会就是加锁。合理的加锁避免线程读写冲突,准确的加锁避免死锁。多线程基本还是在多个线程共享的数据能加锁就加锁,或者直接使用线程安全的类作为数据的类型。
生产者-消费者模型确实在这个单元很有用,处理电梯请求的过程抽象到生产者-消费者模型让我在后面的迭代修改较少。另外输入和调度线程分开也是一种结构清晰的设计,debug起来也方便。
这个单元仅靠总行数不超过100行的代价函数拿下后两次强测的性能分,但是因为线程安全问题互测被hack而且hw7强测挂了一个点,总体来说,分数上我觉得比较满意;学习上我认识了多线程编程的方法还有生产者-消费者模式,也是很有收获。