304
社区成员
发帖
与我相关
我的任务
分享第五次作业是第二单元多线程电梯的起点。输入中每个乘客请求已经指定 BY 电梯号,因此调度器的职责相对简单:输入线程读到请求后封装成事件,分派线程根据请求中指定的电梯编号,把任务转发到对应电梯的请求表。此时主要关注点是基础线程协作、RECEIVE 输出时机、电梯等待与终止。
第六次作业取消了指定电梯,乘客请求需要由程序自行分配。同时加入 MAINT 临时检修请求,检修会改变电梯生命周期:电梯从普通服务状态进入接收检修、维修、测试,期间已有乘客可能 OUT-F 下车并重新调度,等待队列中的任务也可能被撤销 RECEIVE 后重新进入全局调度。因此 hw6 的核心难点从“线程安全转发”变成“线程安全调度 + 状态机 + 重新分配”。
第七次作业进一步加入 UPDATE、RECYCLE 和双轿厢。一个井道内出现主轿厢和备用轿厢两个执行线程,且二者共享井道状态、共享物理空间约束,必须避免同层冲突和穿越冲突。最终代码中,原本集中在 ElevatorThread 内部的状态逐渐被拆出为 CabinState、ShaftContext、DoubleCarRuntime、ControlPhaseRuntime,这是架构演进中最明显的一步。
hw5 的主要结构如下:
InputThread:读取 PersonRequest,写入 DispatcherInbox。DispatcherInbox:输入线程和调度线程之间的共享事件队列。DispatchThread:从事件队列取请求,按指定电梯号投递。DispatchStrategy:直接返回 task.getElevatorId()。ElevatorRequestTable:每部电梯的等待请求表。ElevatorThread:电梯执行线程,负责移动、开关门、上下客。ElevatorStrategy:决定 Wait / Open / Up / Down。PersonTask:乘客任务封装,维护 TaskStage。hw6 在 hw5 基础上增加:
pendingTasks:调度器维护的待分配任务列表。ElevatorDispatchSnapshot:电梯对调度器暴露的快照。ServiceState:Normal / Rep_Accept / Repair / Test。MaintJob、MaintRequestEvent:检修任务封装。RedispatchPersonEvent:乘客重新调度事件。ElevatorUpdateEvent:电梯状态变化后唤醒调度器重新尝试分配。hw7 在 hw6 基础上增加:
ShaftContext:统一管理井道状态、主副轿厢、预约楼层和控制任务。ShaftState:Normal / Rep_Accept / Repair / Test / Up_Accept / Update / Double / Rec_Accept / Recycle。CabinState:单个轿厢的楼层、方向、门、载客状态。DoubleCarRuntime:双轿厢运行逻辑。ControlPhaseRuntime:UPDATE / RECYCLE 的接收期、改造期、回收期逻辑。UpdateRequestEvent、RecycleRequestEvent:控制请求事件。| 共享资源 | 被哪些线程访问 | 生产数据 | 消费数据 |
|---|---|---|---|
DispatcherInbox.queue | InputThread、DispatchThread、各 ElevatorThread | 输入线程加入外部请求;电梯线程加入 ElevatorUpdateEvent / RedispatchPersonEvent | 调度线程 getEvent() |
DispatcherInbox.inputEnded | 输入线程、调度线程 | 输入线程在读到 null 后置位 | 调度线程终止判断 |
ElevatorRequestTable.waitingTasks | 调度线程、电梯线程、控制期 runtime | 调度线程 receiveTask() 加入;部分控制期可能重新入队 | 电梯线程 pollBoardableAtFloor() 消费 |
ElevatorRequestTable.dispatcherClose | 调度线程、电梯线程 | 调度线程关闭请求表 | 电梯线程等待和终止判断 |
ElevatorRequestTable.forceWakeUp | 电梯线程、控制请求处理 | wakeUp() 设置 | awaitIfIdle() 消费并清除 |
CabinState.currentFloor / direction / doorState / passengerCount | 所属电梯线程写,调度线程读快照 | 电梯线程运行时更新 | 调度策略读 ElevatorDispatchSnapshot |
CabinState.passengers | 所属电梯线程为主 | 电梯线程上客加入 | 电梯线程下客移除,策略构造上下文时复制 |
ShaftContext.state | 同井道主/副电梯线程、调度线程 | 调度线程接收控制请求,电梯线程推进状态 | 电梯线程选择运行分支,调度器判断可否分配 |
ShaftContext.mainFloor/spareFloor/reservedFloor | 主副轿厢线程 | 电梯移动前预约,抵达后释放 | awaitMovePermit() 判断是否可进入下一层 |
PersonTask.stage/fromFloor/activeElevatorId | 调度器、电梯线程 | receiveTask()、board()、transferAt() 等修改 | 调度器和电梯策略读取 |
DispatcherInbox 的同步设计DispatcherInbox 是输入层和调度层之间的生产者-消费者队列。关键代码包括:
public synchronized void addEvent(InputEvent event) {
if (dispatcherClosed || event == null) {
return;
}
queue.offer(event);
notifyAll();
}
public synchronized InputEvent getEvent() {
while (queue.isEmpty() && !dispatcherClosed) {
wait();
}
if (queue.isEmpty()) {
return null;
}
return queue.poll();
}
锁对象是 DispatcherInbox 自身。保护变量是 queue、inputEnded、dispatcherClosed。同步块内部操作包括入队、出队、结束标志设置和通知。这里必须加锁,因为 queue.isEmpty()、wait()、queue.poll() 共同构成一个条件等待过程,不能拆开。
我在 hw6/hw7 区分了 inputEnded 和 dispatcherClosed。输入结束后调度器仍可能需要处理电梯重新调度事件,只有全系统空闲后才关闭调度器。
ElevatorRequestTable 的同步设计每部电梯拥有一个 ElevatorRequestTable。调度器向其中写入任务,电梯线程从中查询、捎带和取走任务。
关键方法是 receiveTask():
public synchronized void receiveTask(PersonTask task, int elevatorId) {
TimableOutput.println(String.format("RECEIVE-%d-%d", task.getId(), elevatorId));
task.startReceive(elevatorId);
waitingTasks.add(task);
notifyAll();
}
这里锁对象是对应电梯的请求表。同步块保护 waitingTasks 和 dispatcherClose/forceWakeUp。把 RECEIVE 输出、task.startReceive()、waitingTasks.add() 放在同一个锁内,能够保证一个重要不变量:输出 RECEIVE-x-y 后,这个任务已经进入 y 号电梯请求表,且其 TaskStage 已经从 New 变为 Waiting。
awaitIfIdle() 是第二个重点:
public synchronized void awaitIfIdle(boolean empty) {
while (waitingTasks.isEmpty() && empty && !dispatcherClose && !forceWakeUp) {
wait();
}
forceWakeUp = false;
}
这里的等待条件和 receiveTask()/setDispatcherClose()/wakeUp() 的通知条件共享同一把锁,避免“检查为空后刚要睡眠,调度器已经入队并通知”的漏唤醒问题。
hw5、hw6、hw7 中,电梯开门逻辑都避免在 sleep(openTime) 时持有请求表锁。以 hw7 普通运行的 openAndServe() 为例:
cabinState.setDoorState(DoorState.Open);
TimableOutput.println("OPEN...");
unloadPassengers();
boardPassengers();
sleepMillis(config.getOpenTime());
unloadPassengers();
synchronized (requestTable) {
boardPassengers();
TimableOutput.println("CLOSE...");
cabinState.setDoorState(DoorState.Closed);
}
这里同步块只包住关门前的最后一次上客和关门状态修改。目的是为了在关门前,如果调度器刚好投递了可在当前楼层上车的请求,电梯有机会在同一个临界区内完成最后一次检查和上客,减少 RECEIVE 与 CLOSE 的边界错误。
ShaftContext 的同步设计hw7 的 ShaftContext 是最关键的新锁对象。它保护的是同一个井道内主、副轿厢共享的不变量:
ShaftState;状态转换方法如 acceptUpdateJob()、finishUpdateWithOutput()、acceptRecycleJob()、finishRecycleWithOutput()、acceptMaintJob() 等均为 synchronized。每次状态变化后都 notifyAll() 并调用相关电梯的 wakeForControl()。
双轿厢移动许可的核心在 awaitMovePermit():
public synchronized void awaitMovePermit(boolean isMainElevator, int nextFloor) {
while (!canEnterNextFloor(isMainElevator, nextFloor)) {
wait();
}
if (isMainElevator) {
mainReservedFloor = nextFloor;
} else {
spareReservedFloor = nextFloor;
}
}
它将“能否进入下一层”的检查和“预约下一层”的修改放在同一把锁内,避免两个轿厢同时判断安全、随后同时进入冲突楼层。onArrive() 抵达后清除预约并通知等待线程。
hw5 中,DispatchThread 的职责是:
DispatcherInbox 取输入事件;PersonTask;DispatchStrategy 选择电梯;ElevatorRequestTable.receiveTask();hw6/hw7 中,调度器职责明显扩张:
pendingTasks;RedispatchPersonEvent;ElevatorUpdateEvent 作为重新尝试分配的触发;InputThread 不直接调用调度器方法,而是向 DispatcherInbox 写事件。这样输入和调度解耦,输入线程只负责解析官方包请求:
PersonRequest -> PersonRequestEventMaintRequest -> MaintRequestEventUpdateRequest -> UpdateRequestEventRecycleRequest -> RecycleRequestEvent调度器通常不直接操作电梯内部状态,而是:
ElevatorRequestTable;elevator.acceptMaintenanceJob(),hw7 改为调用 shaftContext.acceptMaintJob();ShaftContext 状态转换入口。电梯线程通过 DispatcherInbox.addEvent() 反向通知调度器:
ElevatorUpdateEvent;RedispatchPersonEvent。这种做法让调度器不需要轮询等待所有电梯状态,而是被状态变化事件唤醒。
Main 负责创建共享对象和线程,启动顺序为:
DispatcherInbox;ElevatorRequestTable 和 ElevatorThread;DispatchThread;join() 输入线程、调度线程和电梯线程。sequenceDiagram
participant I as InputThread
participant Q as DispatcherInbox
participant D as DispatchThread
participant S as DispatchStrategy
participant T as ElevatorRequestTable
participant E as ElevatorThread
I->>Q: addEvent(PersonRequestEvent)
D->>Q: getEvent()
Q-->>D: PersonRequestEvent
D->>D: pendingTasks.add(PersonTask)
D->>S: chooseElevator(task, elevatorSnapshots)
S-->>D: elevatorId
D->>T: receiveTask(task, elevatorId)
T-->>E: notifyAll()
E->>T: pollBoardableAtFloor()
E->>E: OPEN / IN / CLOSE / ARRIVE / OUT
E->>Q: addEvent(ElevatorUpdateEvent)
hw6 中 MAINT 流程为:
InputThread 读到 MaintRequest,封装为 MaintRequestEvent。DispatchThread 调用目标电梯 acceptMaintenanceJob()。Normal 进入 Rep_Accept,请求表 wakeUp()。IN,输出 MAINT1-BEGIN,等待请求表中的任务被 cancelReceive() 后重新投递。Repair,等待 1s 后输出 MAINT2-BEGIN。OUT-S,输出 MAINT-END。Normal,通知调度器。hw7 中 MAINT 状态进入 ShaftContext,ElevatorThread 不再独自维护完整服务状态,而是通过 shaftContext.beginRepairWithOutput()、beginTestWithOutput()、finishMaintWithOutput() 推进状态。
InputThread 将 UpdateRequest 封装为 UpdateRequestEvent。DispatchThread 调用对应井道 acceptUpdateJob()。ShaftContext 从 Normal 进入 Up_Accept,关闭备用轿厢调度,唤醒主轿厢。ControlPhaseRuntime.runUpAcceptStep() 驶向 F3,途中在时间允许时可释放或接纳部分乘客。UPDATE-BEGIN,进入 Update。UPDATE-END。ShaftContext 进入 Double,启用备用轿厢,唤醒主/副轿厢。InputThread 将 RecycleRequest 封装为 RecycleRequestEvent。DispatchThread 调用 ShaftContext.acceptRecycleJob()。Double 进入 Rec_Accept,停止备用轿厢继续接受新任务。ControlPhaseRuntime.runRecycleAcceptStep() 驶向 F1 准备回收。RECYCLE-BEGIN,进入 Recycle。RECYCLE-END。ShaftContext 回到 Normal,备用轿厢不再参与调度。hw5 的外层调度策略是指定电梯策略,DispatchStrategy.chooseElevator() 直接返回 task.getElevatorId()。
hw6/hw7 的外层调度策略是基于评分函数的贪心策略。DispatchStrategy.computeScore() 主要考虑:
权重设置为:
hw5 我的策略更接近主请求/首请求策略:没有乘客时看请求表第一个任务,有乘客时跟随乘客目标方向运行。
hw6 中优化了此策略,让我的最终策略更接近 LOOK:
hw7 中双轿厢阶段还增加了服务范围约束:
OUT-F 后重新调度。MAINT 场景中,电梯进入 Rep_Accept 后会逐步向 F1 靠拢,并在合适情况下释放乘客或继续接纳向 F1 方向的乘客。等待队列中的请求通过 drillAllWaitingTasks() 取出,cancelReceive() 后重新投递,避免检修电梯继续持有任务。
UPDATE 场景中,主轿厢到 F3 后进入改造,改造完成后备用轿厢启用。调度策略通过 ShaftState 限制可服务区间,避免将下半区任务分给未启用的备用轿厢。
RECYCLE 场景中,备用轿厢停止接收新任务,进入回收接收期和静默回收期。调度策略在 Rec_Accept/Recycling 状态下将可服务范围重新收敛。
双轿厢冲突由 ShaftContext.awaitMovePermit() 处理,它检查对方当前楼层、对方预约楼层,以及主副轿厢相对位置,保证不会进入同一楼层或互相穿越。
我的程序整体是“正确性优先,时间次之,能耗再次”的策略。优点是面对 MAINT、UPDATE、RECYCLE 和双轿厢时状态边界比较清楚,不容易为了局部性能破坏合法性。缺点是能耗没有特别考虑,双轿厢跨区采用 F2 重新调度,可能牺牲部分全局最优。
在第六次作业的互测中出现了TLE的相关 bug。
dispatchPendingTasks() 原先只要某部电梯可调度,就持续向它分配;没有考虑其他电梯正在恢复,短时间后可重新承担任务。DispatchThread 中加入 SOLE_DISPATCH_BACKLOG_LIMIT。如果存在恢复中电梯,且当前唯一可调度电梯已有负载达到阈值,就暂缓继续分配。我在这个单元中采用的多线程 Debug 方法:
wait/notify 的条件。ServiceState,hw7 关注 ShaftState。RECEIVE/IN/OUT-F。线程安全不是简单地“所有方法都加 synchronized”。如果锁对象选错,或者同步块没有覆盖完整不变量,程序仍然不安全。我的程序最终主要围绕三类共享资源设锁:
DispatcherInbox 保护输入事件队列;ElevatorRequestTable 保护单部电梯的等待请求表;ShaftContext 保护一个井道内的状态机和双轿厢空间约束。锁保护的是不变量,而不是孤立变量。例如 ElevatorRequestTable 中真正要保护的是“等待队列状态、RECEIVE 生命周期、空闲等待条件”之间的一致性;ShaftContext 中真正要保护的是“主副轿厢不能同时占据或预约冲突楼层”。
条件判断和状态修改必须原子化。awaitMovePermit() 中,如果“判断能否进入下一层”和“预约下一层”之间没有锁,两个轿厢可能同时判断安全并预约同一层。receiveTask() 中,如果 RECEIVE 输出和入队分离,也可能出现输出已经发生但电梯还看不到任务的边界问题。
wait/notify 的本质是基于条件的协作。线程不是因为“有人通知”就一定能继续,而是因为醒来后重新检查条件成立才继续。因此所有等待都应该写成 while (条件不满足) wait()。
三次作业可以分为几个层次:
InputThread,只负责官方输入解析和事件封装。DispatchThread、DispatchStrategy,负责全局分配。DispatcherInbox、ElevatorRequestTable,负责线程间缓冲。ElevatorThread,负责实际移动、开关门、上下客。ShaftContext、CabinState、MaintenanceContext,负责可见状态和状态机。ElevatorStrategy,负责单梯下一步动作选择。TimableOutput 统一输出。调度器不应该直接控制过多电梯内部细节。最终代码中,调度器只拿 ElevatorDispatchSnapshot,不直接修改电梯楼层、方向、门状态,这是较好的边界。
电梯线程不应该关心全局调度策略。电梯只从自己的 ElevatorRequestTable 中取任务,状态变化时发事件通知调度器;具体新请求给哪部电梯由 DispatchStrategy 决定。
请求容器不应该混入复杂业务策略。ElevatorRequestTable 目前承担了等待任务查询、筛选、取出,也有部分接收期辅助方法。若继续重构,可以把复杂筛选逻辑再下沉到策略类或查询对象中。
状态机应该集中管理状态变化。hw7 的 ShaftContext 相比把 UPDATE/RECYCLE 状态散落在多个 if-else 中,更容易保证状态前置条件、状态变化和 notifyAll() 的一致性。
hw5 时,简单的输入队列、调度线程、电梯请求表已经够用。因为电梯由请求指定,调度器只是转发器,很多边界可以由单部电梯内部处理。
hw6 加入检修后,原来的结构暴露出扩展性问题:电梯不仅有“有无请求”,还有“是否可服务”;乘客不仅会完成,还可能中途失败并重回调度;调度器也不能只看输入队列是否结束,还要看电梯是否都恢复空闲。
hw7 加入双轿厢后,边界必须重新划清。一个井道不再等价于一个电梯线程,主副轿厢之间需要共享状态,而这个共享状态不能由任一轿厢单独持有。因此 ShaftContext 成为必要抽象。
classDiagram
direction LR
class Main
class InputThread {
<<thread>>
+run()
}
class DispatchThread {
<<thread>>
-pendingTasks
+run()
-dispatchPendingTasks()
-handleEvent()
-canShutdown()
}
class ElevatorThread {
<<thread>>
+run()
+wakeForControl()
+isIdleForShutdown()
+getDispatchSnapshot()
}
class DispatcherInbox {
-queue
-inputEnded
-dispatcherClosed
+addEvent()
+getEvent()
+setInputEnded()
+closeDispatcher()
}
class ElevatorRequestTable {
-waitingTasks
-dispatcherClose
-forceWakeUp
+receiveTask()
+pollBoardableAtFloor()
+drillAllWaitingTasks()
+awaitIfIdle()
}
class DispatchStrategy {
+chooseElevator()
+getSoleDispatchableElevatorId()
+canDispatchTask()
}
class ElevatorStrategy {
+nextAction()
+determineBoardingDirection()
+nextDirectionAfterService()
}
class ShaftContext {
-state
-mainFloor
-spareFloor
-mainReservedFloor
-spareReservedFloor
+acceptUpdateJob()
+beginUpdateWithOutput()
+finishUpdateWithOutput()
+acceptRecycleJob()
+finishRecycleWithOutput()
+acceptMaintJob()
+awaitMovePermit()
+onArrive()
}
class CabinState {
-currentFloor
-doorState
-direction
-passengerCount
-passengers
}
class DoubleCarRuntime {
+runDoubleStep()
+moveOneFloorInDouble()
}
class ControlPhaseRuntime {
+runUpAcceptStep()
+runUpdateSilentStep()
+runRecycleAcceptStep()
}
class ElevatorDispatchSnapshot
class PersonTask {
-stage
-fromFloor
-toFloor
-activeElevatorId
+startReceive()
+cancelReceive()
+board()
+transferAt()
+finishAtDestination()
}
class InputEvent
class PersonRequestEvent
class MaintRequestEvent
class UpdateRequestEvent
class RecycleRequestEvent
class RedispatchPersonEvent
class ElevatorUpdateEvent
class MaintJob
class ShaftState {
<<enum>>
Normal
Rep_Accept
Repair
Test
Up_Accept
Update
Double
Rec_Accept
Recycle
}
class TaskStage {
<<enum>>
New
Waiting
Onboard
Finished
}
class TimableOutput
Main --> InputThread
Main --> DispatchThread
Main --> ElevatorThread
InputThread --> DispatcherInbox
DispatchThread --> DispatcherInbox
DispatchThread --> DispatchStrategy
DispatchThread --> ElevatorRequestTable
DispatchThread --> ShaftContext
DispatchThread ..> ElevatorDispatchSnapshot
ElevatorThread --> ElevatorRequestTable
ElevatorThread --> ElevatorStrategy
ElevatorThread --> CabinState
ElevatorThread --> ShaftContext
ElevatorThread --> DoubleCarRuntime
ElevatorThread --> ControlPhaseRuntime
ShaftContext "1" o-- "2" ElevatorThread
ElevatorRequestTable o-- PersonTask
CabinState o-- PersonTask
InputEvent <|-- PersonRequestEvent
InputEvent <|-- MaintRequestEvent
InputEvent <|-- UpdateRequestEvent
InputEvent <|-- RecycleRequestEvent
InputEvent <|-- RedispatchPersonEvent
InputEvent <|-- ElevatorUpdateEvent
ShaftContext --> ShaftState
PersonTask --> TaskStage
ElevatorRequestTable ..> TimableOutput
ElevatorThread ..> TimableOutput
ShaftContext ..> TimableOutput
Main 是启动层,负责创建 DispatcherInbox、所有电梯线程、所有请求表、所有井道上下文,并完成线程启动与 join()。
InputThread 是输入层,只负责调用官方输入接口,把不同类型的请求包装为 InputEvent 子类,然后放入 DispatcherInbox。
DispatcherInbox 是输入线程、电梯线程和调度线程之间的共享事件队列,是典型生产者-消费者缓冲区。
DispatchThread 是调度层,负责维护 pendingTasks、调用 DispatchStrategy、处理 RedispatchPersonEvent 和控制请求,并判断系统终止。
DispatchStrategy 是全局分配策略,只基于 PersonTask 和各电梯的 ElevatorDispatchSnapshot 评分,不直接修改电梯状态。
ElevatorRequestTable 是调度器与单部电梯之间的请求缓冲区,负责 RECEIVE 输出、任务入队、候乘查询、上客取出、空闲等待。
ElevatorThread 是执行层,负责普通运行、检修流程、控制期流程的分派。hw7 中它把双轿厢运行委托给 DoubleCarRuntime,把 UPDATE/RECYCLE 控制期委托给 ControlPhaseRuntime。
CabinState 是单个轿厢的状态对象,保存当前楼层、门状态、方向、载客数和乘客列表。
ShaftContext 是 hw7 的井道共享状态对象,是主副轿厢线程之间最重要的锁对象。它统一管理 ShaftState、主副轿厢楼层、预约楼层、F2 等待标记和控制请求生命周期。
PersonTask 是乘客生命周期对象。它用 TaskStage 表示 New -> Waiting -> Onboard -> Finished,在 OUT-F 或检修/回收中途下车时通过 transferAt() 回到 New,重新进入调度器。
稳定依赖包括:输入层依赖事件队列、调度层依赖快照和请求表、执行层依赖请求表和策略、状态层被执行层和调度层读取。为适配迭代变化引入的依赖主要是 hw7 的 ShaftContext、DoubleCarRuntime、ControlPhaseRuntime,它们将双轿厢和控制请求从原本单轿厢模型中拆出。
我使用的 AI 为 codex gpt5.5 xhigh。
在这个单元中,由于对一开始对多线程的概念较为模糊,我大量求助AI有关于多线程与锁之间的问题,很好的帮我理清了相关概念。
在后续作业开发中,我会首先提出我的架构设计,在与 AI 探讨后由 AI 生成一份代码骨架,我根据骨架完成代码。在 hw6 以及 hw7 中,我主要负责完成基本的功能部分,在通过弱侧和中测后让 AI 帮助我进行性能上的优化以及 code review。
我的使用感受就是 AI 能够基本上完成作业的要求,帮助我在时间紧迫时顺利完成一份质量还不错的代码。不过在审查部分,AI 无法照顾到每个细节,尤其是后期要求越来越复杂,有很多隐式的 bug 未能发现并解决,也会讲一些性能问题误判为正确性问题。
在这一单元中,我使用 AI 的频率和次数相较于上一单元大幅增加,这提高了我的工作效率,不过我也认为在使用 AI 的同时我自己的思考有所欠缺,似乎从自己写一份代码变成了理解 AI 的代码。尤其是当 AI 大量改动时,我并没有对每一个方法都进行自己的审查,只是直接用评测机等方法进行黑盒测试和回归测试,失去了锻炼代码能力的机会。
经过这个单元的学习后,我对多线程并发有了更清晰的了解,对生产者-消费者模型也有了基本的掌握。
电梯一直像一个恐怖怪谈一样传递在每一届 6 系中间,但由于我大量使用了 AI,我并没有在这一单元遇到很大的麻烦,对代码的理解与掌握程度肯定也不如原来的学长学姐们,需要我反思在后续如何平衡好投入时间和代码效率。
在 hw5 中,我的多线程和共享对象架构都较为清晰,也是我对多线程理解最透彻的时候。但在后续作业里,感觉考察的重点由多线程转变为状态机设计、边界情况考虑和分派策略的优化,许多同学把大量的时间投入在了性能优化中,对多线程本身的考察可能不多,感觉不太契合单元主题。这点从互测阶段也能看出来,大部分同学攻击的都是由于分配策略导致的超时问题而不是多线程死锁相关的问题,是否可以增加对多线程本身的考察呢?