309
社区成员
发帖
与我相关
我的任务
分享多线程编程与单线程编程的思维方式截然不同。本单元的电梯系统迭代从最初的单部固定电梯,一路迭代到需要统筹全局的调度器,再到引入临时检修状态机以及极其复杂的双轿厢换乘机制。以下是我对这三次作业的复盘与总结。
在多线程环境下,保护共享资源的安全性是第一要务。在我的架构中,主要的共享资源是请求队列(RequestQueue)。
同步块与Wait/NotifyAll机制: 为了避免轮询带来的性能惩罚甚至是TLE,我主要采用了 synchronized 关键字配合 wait() 和 notifyAll() 方法。输入线程、调度器线程和电梯线程都会尝试获取共享队列的锁。当电梯发现自己的候乘队列为空且系统尚未结束时,会主动调用 wait() 挂起;而一旦调度器将新请求放入该队列,或者输入线程向主队列投入了新请求,就会调用 notifyAll() 唤醒处于等待状态的线程去竞争锁并获取任务。 锁与处理语句的关系:同步块内部的代码必须是“快进快出”的。在同步块中,我只进行数据的读取、修改和状态的判断,绝不在此进行耗时的操作(例如电梯的sleep移动模拟)。耗时操作全部放在锁的外部,以保证并发度。
特殊的防碰撞锁(双轿厢换乘层): 在后续引入双轿厢电梯(共用同一井道,在特定楼层如F2换乘)时,基础的队列锁已经无法满足要求。两部轿厢绝对不能同时处于换乘层。为此,我专门为换乘楼层对象引入一把互斥锁。当A轿厢需要驶入换乘层时,必须先尝试获取这把锁,如果B轿厢已经占用,A轿厢则必须在换乘层外等待,直到B轿厢离开并释放锁。
三级流水线与交互方式: 随着作业要求的升级,我构建了“输入线程 -> Dispatcher调度器线程 -> 电梯线程”的三级流水线架构。
输入线程:唯一的生产者,负责接收标准输入,将解析后的请求无脑丢入系统的“总等待队列”。
调度器线程:扮演总线中枢的角色。它从总队列中取出请求,根据当前的电梯状态,将请求分发给各个电梯私有的“子等待队列”。
电梯线程:纯粹的消费者。它们只关心自己的子队列,按照自己的运行策略移动并接送乘客。 这种设计实现了高内聚低耦合,调度器不需要知道电梯具体怎么跑,电梯也不需要知道外面的请求是怎么来的。
调度策略与性能权衡(时间与电量): 电梯的单体运行策略上,我采用了基于 ALS的算法,保证在同一方向上尽可能多地捎带乘客。 在宏观的调度策略上,为了兼顾运行时间和耗电量,调度器在分配请求时会综合评估各电梯的“代价”。如果某部电梯正在检修,调度器会立即停止向其分配新请求,并将该电梯被强制清空出来的乘客重新回收到总队列中,进行二次分配。在双轿厢场景下,由于双轿厢拥有更高的并发运载能力,调度器会适当给予其更高的权重。
典型Bug分析:
TLE与死循环:在处理双轿厢换乘或者乘客因检修被赶下电梯时,曾出现过逻辑死循环。乘客不断地被放入队列,又因为不符合上车条件被弹出,导致电梯在同一楼层疯狂开关门。
线程无法正常停止:当输入结束后,输入线程发出结束信号。但由于检修状态机(NORMAL -> ACCEPT -> REPAIR -> TEST -> NORMAL)的复杂性,部分电梯在完成检修后,没有正确收到系统结束的广播,导致一直处于 wait() 状态,无法使进程正常退出。
检修清空乘客异常:在收到 MAINT1-BEGIN 信号时,未能安全且原子地清空轿厢内乘客,导致部分乘客状态丢失,最终无法完成所有请求。
多线程Debug方法: 多线程的Bug通常难以复现。我放弃了传统的断点调试,转而使用时间戳日志法。通过在每一个获取锁、释放锁、wait、notify、以及状态转移的地方打印带有时间戳和线程名的详细日志,将日志导出到文件,然后写一个简单的Python脚本去验证时间线和锁的互斥逻辑,这比单步调试高效得多。
线程安全:不仅是程序不崩溃,更是逻辑的严密。任何一个共享变量的读写,都必须思考当前是否有其他线程正在操作。线程安全的本质在于对并发访问的合理序列化。
层次化设计:这是应对需求变更的利器。因为我将电梯的行为与调度器的分配逻辑严格分层,所以在面对新增“检修”指令和“双轿厢”改造时,电梯线程的代码框架几乎不需要大改,只需要在调度器层面增加回收逻辑,在电梯状态机中插入检修流程即可。
模型名称:Gemini
分工与优势:在这次极其复杂的电梯任务中,我主要让大模型辅助我进行“状态机设计”和“正则表达式/输入解析”的构建。面对诸如检修流程这种包含多个状态流转的需求,大模型能很快帮我搭建出状态模式的骨架代码。同时,在排查死锁概念时,大模型的解释非常通透。
遇到的困难:大模型在处理这种带有极强业务约束的多线程调度时,往往会给出看似正确但实际上暗藏数据竞争的并发代码。我不能直接复制大模型的线程控制代码,必须自己去精细控制 wait/notify 的位置。它是一个极好的架构顾问,但具体的锁机制还需亲自操刀。
感受:第二单元绝对是OO课程中“痛并快乐着”的巅峰。看着多部电梯在控制台中按照预期的逻辑井然有序地上下翻飞、完美避让、按时检修,那种成就感是单线程编程无法比拟的。同时,因为多线程Bug的随机性,熬夜看Log查死锁的经历也让人印象深刻。
建议:
希望课程组在下发指导书时,可以提供一个简单的可视化工具或者日志分析脚本的范例。单纯看海量的文本输出,对初次接触多线程的同学来说过于折磨。
检修状态机中对于乘客“清空与重分配”的边界条件描述可以再详细一些,比如检修结束时,原先被赶下车的乘客是否具有上车的优先权等。