OO第二单元总结-代价函数的大胜利和线程安全的大失败

周运贵-22371276 学生 2024-04-19 15:44:17

架构分析

  • 类图

  • 时序图

基本架构:

输入线程将输入加入等待队列

调度线程在等待队列中取出请求加入选择的电梯,在等待队列没有请求但是双轿厢电梯仍可能放出新请求时等待(其实是不断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同步了但是开关门没同步~~

 

程序出现过的bug以及面对多线程程序的debug方法

  • 一开始有过ctle问题,就是轮询,这个很好找,在while里面各种地方print都能找到;
  • 后来主要是线程安全问题(这东西没有服务器真的是除了读代码我啥都不会),我只是自己尝试分析会被共享的数据在哪里,在各个线程运行时会调用什么方法访问这些数据,然后无脑加锁,可惜最后还是没加够,还是直接使用线程安全的CopyOnWriteArrayList好啊;
  • 还有过rtle问题,在第七次作业中,电梯会卡死在边界楼层不动,我在look策略尝试翻转的方法里加入了临界楼层判断翻转的逻辑就好了;
  • debug方法都是dpo + 自己在代码里加print输出相关信息,这里还挺感谢弄dpo的佬的。print的方法不难。电梯不结束就在电梯结束时输出电梯编号,看看是哪里卡死了,然后针对卡死的电梯有什么特别之处,在有可能执行到的地方输出电梯相关信息(电梯里有没有人,请求池是否空,电梯位置方向等)来看看是什么条件会卡死。总之,只要有bug数据、可以复现,debug都是不难的。

 

心得体会

  • 线程安全

我对线程安全的体会就是加锁。合理的加锁避免线程读写冲突,准确的加锁避免死锁。多线程基本还是在多个线程共享的数据能加锁就加锁,或者直接使用线程安全的类作为数据的类型。

  • 层次化设计

生产者-消费者模型确实在这个单元很有用,处理电梯请求的过程抽象到生产者-消费者模型让我在后面的迭代修改较少。另外输入和调度线程分开也是一种结构清晰的设计,debug起来也方便。

个人总结

这个单元仅靠总行数不超过100行的代价函数拿下后两次强测的性能分,但是因为线程安全问题互测被hack而且hw7强测挂了一个点,总体来说,分数上我觉得比较满意;学习上我认识了多线程编程的方法还有生产者-消费者模式,也是很有收获。

 

 

 

 

...全文
47 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧