一.同步块与锁的设置和分析

在源代码中,我主要使用了 synchronized 关键字实现对象锁,以保证多个线程(调度器、电梯、输入线程、总队列、电梯私有队列)对共享资源访问的原子性和可见性。

我的程序采用的架构如下图所示:

从架构中可看出,在不同线程间存在共享的数据是Totalqueue与Waitqueue,故这两部分涉及到的同步块与锁较多。

TotalQueue

位置:Dispatcher.run() 方法中。

Dispatcher.run()方法通过wait 释放锁并进入阻塞,直到被总队列TotalQueue中的addRequest(有新的request进入)和setend(输入结束)唤醒,确保从总队列获取请求的操作是线程安全的。

WaitQueue

位置:分散在 Dispatcher canFitProjectedgetPathExtreme 以及 Elevator runshouldOpenactionOpenAndClose 和异常情况处理的相关方法。涉及对私有队列的遍历、添加请求、移除请求以及 wait/notifyAll。关系:这是最繁忙的锁。调度器在分配请求时需锁住它来预判载重;电梯在运行时需锁住它来决定是否开门接客或进入休眠等待。

TotalQueue是关键的生产者-消费者模型实现:

  • TotalQueue为空时,Dispacther处于wait
  • addRequest在添加请求后立即notifyAll(),唤醒等待的调度线程
  • setend()在任务完成后notifyAll(),使得调度线程能及时检测到所有任务完成

二.调度器与其他线程的交互

在前面的架构图中,可以很清晰的看到:调度器与Totalqueue,elevator以及Waitqueue均有交互。

  • 与输入线程交互(被动唤醒): 调度器在 run 方法中对 totalQueue 加锁并执行 wait()。当输入线程向 totalQueue 添加请求并调用 notifyAll() 时,调度器被唤醒。

  • 与电梯线程交互(主动分发): 对于常规Request,调度器通过 findBestElevator 计算出最优电梯 ID 后,调用该电梯私有 waitQueueaddRequest 方法。此操作会触发 waitQueuenotifyAll(),从而唤醒处于休眠状态的电梯线程。

  • 终止交互: 当输入结束且总队列为空时,调度器调用 terminateElevators(),遍历所有电梯并将其 waitQueueisEnd 标志设为 true,引导电梯线程正常结束循环。

三.调度策略

第五次作业中,request指定了接受电梯ID,故无调度策略,只需根据request信息机械分配既可。

第六次作业中,我使用了一套动态影子电梯模拟分配策略,具体如下:

核心分配流程

状态初筛:首先过滤掉状态不为NORMAL(如正在检修或准备检修)的电梯。

载重预判 (canFitProjected): 这是策略中的重点。调度器不只是看当前重量,而是累加电梯 WaitQueue如果接受了该Request,则在接人途中要捎带的全部人员重量。如果 当前重量 + 已分配重量 + 新请求重量 > 400,则该电梯被排除。

物理 ETA 计算 (calculatePhysicaleta)

基于 LOOK 算法逻辑模拟运行距离:当前反向或已越过:计算电梯运行至当前方向最远端点(轿内终点或队列起点)后再折返的距离。同向顺路:直接计算层差。性能系数:最终得分为0.4\cdot Distance + 0.3\cdot Weight.

其中,Distance为运行距离,Weight为当前电梯Waitqueue队列中已有的Request数量,用来衡量电梯负载程度。

性能指标

通过 ETA 计算选择物理距离和任务负载综合最优的电梯,能同时考虑最好的平衡平均运行时间和电梯的负载程度,节省时间,电量。

出现的BUG

第五次作业

电梯判断是否在当前楼层开门的逻辑shouldopen有问题:只要当前电梯没超载且当前楼层有请求就开门,导致如果当前楼层存在一个电梯载入后就会超重的请求,电梯会判断为开门,但又没有除载客外忽视当前请求的机制,导致电梯在当前楼层陷入开关门流程的死循环。

解决方法:将shouldopen的逻辑改为存在可载(加上当前重量不会超载)的请求才判定应该开门。

第六次作业

电梯线程在进入检修态后要将私有队列中的请求回收至Totalqueue中,因为没有加适当的锁,导致与Dispatcher线程竞争Totalqueue,导致线程不安全,表现为部分Request会被“无视”。

解决方法:避免elevator线程直接写入Totalqueue,改为将request回收至自己的私有队列中,检修结束后再重新输出Receive信息。

大模型使用情况

在本单元的作业中,除了线程安全相关代码需自己认真了解外,其余代码包括调度策略的具体算法以及elevator线程处理异常request(如MAINT)的流程方法,我均交由大模型生成。大模型给出的整体框架没有大的问题,可直接使用。

局限性:在第六次作业中,大模型在输出调度策略部分,在给出了明确要求(MAINT期间elevator不允许receive)情况下,大模型依然“画蛇添足”地给出了在所有电梯都满载或处于检修状态的极端情况下等待500毫秒直接将request分配给1号电梯的错误兜底策略,导致1号电梯可能在MAINT期间输出Receive信息。大模型很多时候可能表面遵循了你给出的要求,实则在某个逻辑角落间接违反规则,埋下隐患。且给出的调度策略让人觉得经验成分过高,比如直接引入许多系数来平衡电梯距离和负载而不告诉用户具体细节和考虑,可读性不高,且显得杂乱无章。

真实感受

第二单元学习强度很大,涉及到许多新的理论知识,且调试难度很高

我在第六次作业中,因线程不安全问题反复出现了本地运行结果正确,评测机输出报错的情况,一开始毫无头绪,根本无法定位问题所在,耗费很长时间才找到因Request回收导致的线程不安全问题。

且第二单元公测强度感觉较低,我在第五,六次作业均出现了简单问题通过公测未能反映出来,导致强侧得分低的情况。其中第五次作业的公测甚至对于满载这种情况都未涉及。希望可以提高公测数据点强度。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐