第二单元博客总结-多线程

田云恺-24231207 2026-04-24 19:39:31

一、前言

第二单元的电梯作业让我第一次比较系统地感受到并发程序的复杂性。回头看这三次作业,我最大的变化不是语法或框架层面的熟练,而是思考方式发生了变化:我不再把问题理解为“某个函数写错了”,而是更愿意从线程之间如何协作、共享状态如何被访问、系统在极端输入下会不会失稳来分析。尤其在强测、互测和研讨课之后,我越来越确认一件事:并发程序里多数 bug 都不是孤立点状错误,而是由锁设计、调度策略和线程交互方式共同造成的系统性问题。

二、三次作业中同步块设置与锁选择

从三次作业的迭代过程来看,我对同步块和锁的理解是逐步加深的。第一次作业时,我的目标很直接:先把正确性兜住,所以同步块范围偏大,锁也比较粗,基本思路是“宁可慢一点,也不能乱”。这种做法在早期确实有效,但很快就暴露出吞吐下降、线程等待时间偏长的问题。到了第二次作业,我开始尝试把共享状态按职责拆开,把请求池、线程状态、调度信息分开管理,锁粒度也随之变细。第三次作业中,我更加关注临界区长度,尽量把真正需要互斥的状态读写留在锁内,把路径计算、日志处理等非共享语句移到锁外,整体并发性明显改善。

同步块和锁中语句的关系,在这三次作业里给我最深的体会是:锁的本质不是“包住一段代码”,而是“保护一组共享不变量”。如果同步块里塞了太多与共享状态无关的计算,就会增加无效阻塞;如果把本应原子完成的状态更新拆散,又很容易出现竞态。因此我后期会优先保证两点:第一,同一类共享资源始终走同一个加锁入口,避免“有时加锁有时不加锁”的隐患;第二,状态变更和队列操作尽量放在同一原子片段内,避免线程切换时出现中间态被观察到的问题。

从实际踩坑看,同步块过大和同步块过碎都不好。前者会“稳但慢”,后者会“快但乱”。多共享对象交叉访问时,如果缺少统一加锁顺序,还可能带来死锁风险。这些问题并不是某一次作业独有,而是贯穿整个单元,所以我最后更倾向于把锁设计当作架构问题来处理,而不是修补式修改。

 三、调度器设计、线程交互与调度策略

我的调度器整体采用“集中决策、分发执行”的思路。输入线程负责把请求写入共享请求池,调度线程持续从请求池中取任务并进行分配,电梯线程则维持自身状态机独立运行。这种设计在实现上不算花哨,但边界比较清楚:输入只管生产请求,调度只管决策,电梯只管执行。职责分离后,定位 bug 时会更有方向,不至于在一个大循环里同时改三类逻辑。

调度器和电梯线程的交互方式上,我主要依赖线程安全共享结构和条件等待机制。系统空闲时,线程通过等待降低空转;有新请求时,再通过唤醒恢复调度。这里我后来特别注意一点:调度器不直接“操控”电梯动作,而是给出任务,电梯线程根据自身状态推进执行。这样虽然在实现上多了一层间接性,但可维护性会更好,也避免了调度逻辑过度耦合到执行细节。

在调度策略上,我尝试同时兼顾时间、电量和稳定性三个指标。时间上优先考虑顺路性和方向一致性,减少乘客等待;电量上尽量减少无意义折返和频繁启停;稳定性上避免极端贪心导致边缘请求长期得不到服务。实践下来,我的策略更像是在“局部最优”和“全局公平”之间做平衡:先计算代价,再叠加等待时间修正。它不是最激进的最短路策略,但在测试中表现更稳定,特别是在请求分布不均或突发密集时,能更好地避免系统抖动。

四、出现过的 bug 与我的多线程 debug 方法

我在这一单元遇到过的 bug 大致有三类。第一类是请求重复处理,这通常出现在队列操作和状态位更新不在同一个原子区域时;第二类是线程“看起来没死锁但就是不动”,本质往往是等待条件不完整或唤醒后没有重新校验状态;第三类是结束条件判断失真,比如输入结束标志和请求池清空的判断顺序不当,导致线程提前退出或迟迟不退出。还有一些 bug 在本地很难复现,只在高并发或特殊时序下触发,这类问题最耗时间。

针对这些问题,我逐步形成了一套比较固定的调试流程。先做“问题分层”,先验证线程安全,再调策略表现,避免把功能问题和性能问题混在一起。然后在关键路径打结构化日志,至少包含线程名、关键状态、请求池规模、方向与目标层,保证能还原时序。接着我会构造极端样例,比如同层爆发请求、反向连续请求、临界结束时刻请求,专门去打容易出错的边界。最后用可复现实验法把输入固定,重复跑同一组数据,对比日志定位不稳定点。这套方法不一定最快,但在多线程场景下比较稳。

五、从三次作业谈线程安全与层次化设计

做完三次作业后,我对线程安全的理解明显从“语法层面”转到了“协议层面”。线程安全并不等于“哪里不放心就加锁”,而是先定义清楚共享资源边界,再规定访问协议,并保证状态转换的原子性。尤其在有多个条件共同决定线程行为的场景里,正确处理可见性和等待条件,比扩大同步块更关键。很多看似偶发的问题,本质上都是访问协议不完整。

层次化设计方面,我后期基本把系统拆成输入层、调度层、执行层和数据层。输入层负责请求进入系统,调度层负责决策分配,执行层负责电梯状态机,数据层负责共享结构与线程通信。这样的分层让修改成本降低了很多:调策略时主要动调度层,改电梯行为时主要动执行层,不会再出现“一处修改牵动全局”的连锁反应。对我来说,这也是本单元最大的工程收获之一。

六、大模型使用心得

在实际开发中,我把它定位为“辅助决策和排查的工具”,而不是“直接替我写完系统的主体”。通常我会自己先确定基本架构和线程边界,再让模型参与方案比较、边界条件检查、伪代码草拟以及测试样例补充。最终代码、并发正确性和性能验证仍由我自己把关。

在多线程电梯这种任务里,大模型的优势主要体现在三个方面。第一,它能很快给出多个可选思路,帮助我避免一开始就把自己锁死在某个实现路径里;第二,它在并发风险提醒上很有价值,像死锁、活锁、可见性、虚假唤醒这些点,模型经常能及时补充;第三,在 debug 阶段它可以给出结构化排查清单,节省我来回试错的时间。

当然,困难也很明显。模型有时会给出“逻辑上自洽但不贴合题目语义”的方案,尤其在细节约束比较多时更容易出现这种情况。另外,多线程问题对时序细节非常敏感,而模型在这方面并不总是可靠。如果提示词写得泛,输出就容易泛,落地价值会明显下降。整体而言,我的感受是:大模型非常适合做高效助手,但不能替代开发者对并发正确性的最终判断。对于这种复杂任务,最稳妥的方式仍然是“模型给建议,人来做验证”。

 七、第二单元的真实体验与建议

这一单元给我的真实感受是“压力很大,但成长也很快”。它和前面单元最大的不同在于,代码不仅要功能正确,还要在并发场景下稳定、可解释、可维护。很多时候以为已经写对了,结果一上强测或互测就暴露出时序问题。这个过程虽然挫败感很强,但也逼着我从“做题思维”转向“系统思维”,开始主动考虑边界、性能和工程结构。

如果提建议,我觉得可以在教学侧再增加一些“并发问题图谱化”的内容。比如把常见错误模式(竞态、死锁、结束条件错误、唤醒条件缺失)整理成更直观的案例集合,配上最小复现示例,会对同学很有帮助。另外,研讨课如果能多展示“同题不同架构”的优劣对比,也能让大家更快建立设计权衡意识。最后,在作业说明中若能更明确地给出性能指标与策略之间的关联示例,同学在优化时会更有方向感。

八、结语

第二单元让我更深地理解了并发编程的本质:它不是“开几个线程”这么简单,而是对协作规则、状态一致性和工程分层的系统设计。回头看三次作业,我觉得自己真正学到的,不只是完成一次电梯模拟,而是如何在复杂约束下构建一个可运行、可维护、可调试的并发系统。这些能力不仅对应当前课程,也会直接影响后续更大规模的软件实践。

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

309

社区成员

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

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