labs - and - drills
Labs and Drills:四大件可操作练习
这份文件补足一个关键问题:只读文章不够,读者需要能自己观察现象。
这里的练习不绑定某个语言栈,也不要求复杂环境。每个练习都按同一结构写:
目标
观察点
操作
预期现象
反思问题
对应章节
0. 怎么做这些练习
建议每个练习都写一份简短记录:
练习名称:
我的输入:
观察到的现象:
我以为会怎样:
实际为什么这样:
对应的底层机制:
这会让学习从“看懂”变成“能解释”。
Lab 01:同一段 bit 的不同解释
| 项目 | 内容 |
|---|---|
| 目标 | 理解 bit 本身没有意义,意义来自解释规则 |
| 观察点 | 同一段字节可以解释成整数、字符、指令或格式字段 |
| 操作 | 选一段二进制模式,例如 01000001,分别按字符、无符号整数、有符号整数解释 |
| 预期现象 | 同一段 bit 得到不同含义 |
| 对应章节 | 00-cser-foundations.md、computer-organization/01-binary-data-cpu-memory.md |
反思问题:
- 数据类型到底保存了什么信息?
- 文件格式和网络协议为什么必须定义字段解释规则?
- 为什么乱码、解析失败、协议错位本质上都是解释规则错了?
Lab 02:整数溢出的边界
| 项目 | 内容 |
|---|---|
| 目标 | 观察固定位宽整数的边界行为 |
| 观察点 | 最大值再加 1 后,位模式如何变化 |
| 操作 | 选择 8 位或 32 位整数模型,手算最大值附近的加法 |
| 预期现象 | 超出范围后按固定位宽截断,解释成有符号数时可能变负 |
| 对应章节 | computer-organization/02-integer-complement-overflow.md |
反思问题:
- 为什么机器整数不是数学整数?
- 为什么长度、容量、偏移量计算要警惕溢出?
- 为什么边界测试必须包含最大值和接近最大值的组合?
Lab 03:浮点误差观察
| 项目 | 内容 |
|---|---|
| 目标 | 理解二进制浮点近似 |
| 观察点 | 十进制小数转成二进制后可能无法精确表示 |
| 操作 | 选 0.1、0.2、0.3,比较十进制期望和二进制浮点近似结果 |
| 预期现象 | 直接相等比较可能失败 |
| 对应章节 | computer-organization/03-floating-point.md |
反思问题:
- 为什么不能直接用浮点做精确相等判断?
- 什么场景可以接受 epsilon?
- 什么场景应该使用整数化单位或定点表示?
Lab 04:顺序访问 vs 随机访问
| 项目 | 内容 |
|---|---|
| 目标 | 观察局部性对性能的影响 |
| 观察点 | 顺序访问更容易利用缓存行和预取 |
| 操作 | 准备一批连续数据,分别顺序访问和随机访问,记录耗时差异 |
| 预期现象 | 随机访问更容易慢,即使操作数量相同 |
| 对应章节 | computer-organization/06-memory-hierarchy.md、computer-organization/07-cache.md |
反思问题:
- 为什么复杂度一样,速度仍然不同?
- 数据结构的内存布局如何影响缓存命中?
- 为什么链式结构灵活,但可能不如连续结构缓存友好?
Lab 05:动态数组扩容抖动
| 项目 | 内容 |
|---|---|
| 目标 | 观察摊还复杂度和单次抖动的区别 |
| 观察点 | 多数插入便宜,扩容那一次需要复制大量元素 |
| 操作 | 模拟动态数组容量从 1、2、4、8 增长,每次扩容记录复制数量 |
| 预期现象 | 平均成本低,但某些插入成本高 |
| 对应章节 | data-structures/02-array-dynamic-array.md |
反思问题:
- 摊还 O(1) 为什么不等于每次 O(1)?
- 什么时候需要预估容量?
- 为什么尾延迟比平均值更敏感?
Lab 06:哈希冲突和扩容
| 项目 | 内容 |
|---|---|
| 目标 | 理解哈希表平均 O(1) 的条件 |
| 观察点 | 冲突、负载因子、rehash 会影响性能 |
| 操作 | 构造多个 key 映射到同一桶,观察查找链路;再模拟扩容后重新分布 |
| 预期现象 | 冲突多时查找退化,扩容时发生集中成本 |
| 对应章节 | data-structures/05-hash-table.md |
反思问题:
- 哈希函数质量为什么重要?
- 为什么负载因子不能无限增大?
- 为什么恶意 key 可能攻击哈希表性能?
Lab 07:队列堆积和反压
| 项目 | 内容 |
|---|---|
| 目标 | 理解队列只能吸收短期速度差 |
| 观察点 | 生产速度大于消费速度时,队列长度持续增长 |
| 操作 | 设定生产者每秒放入 100 个任务,消费者每秒处理 80 个任务,计算队列增长 |
| 预期现象 | 队列越来越长,等待时间越来越高 |
| 对应章节 | data-structures/04-stack-queue.md、operating-systems/11-performance-debugging.md |
反思问题:
- 为什么队列不是性能瓶颈的最终解法?
- 什么时候需要限流、丢弃、降级或扩容消费者?
- 重试如何进一步放大队列压力?
Lab 08:进程状态和等待
| 项目 | 内容 |
|---|---|
| 目标 | 区分运行、就绪、阻塞和等待 |
| 观察点 | CPU 不高但程序慢,通常说明程序在等 |
| 操作 | 画一个任务从运行到阻塞 IO,再被唤醒继续运行的状态图 |
| 预期现象 | 任务生命周期不是连续占用 CPU |
| 对应章节 | operating-systems/01-process-thread-cpu.md |
反思问题:
- 为什么 CPU 低不代表系统没问题?
- 谁负责把阻塞任务唤醒?
- 为什么大量短任务会增加调度成本?
Lab 09:死锁等待图
| 项目 | 内容 |
|---|---|
| 目标 | 通过等待图识别死锁 |
| 观察点 | 等待关系出现环 |
| 操作 | 画两个任务、两把锁:任务 A 持有锁 1 等锁 2,任务 B 持有锁 2 等锁 1 |
| 预期现象 | 等待图有环,两个任务都无法推进 |
| 对应章节 | operating-systems/04-concurrency-sync.md、foundations/08-concurrency-time-order.md |
反思问题:
- 死锁的四个必要条件是什么?
- 为什么统一加锁顺序能降低死锁风险?
- 超时和回滚能解决什么,又会带来什么复杂度?
Lab 10:write 成功和持久化完成的区别
| 项目 | 内容 |
|---|---|
| 目标 | 理解文件写入路径和崩溃窗口 |
| 观察点 | write 返回不等于数据一定落到持久介质 |
| 操作 | 画出应用缓冲、内核页缓存、文件系统、块设备、存储介质的路径 |
| 预期现象 | 每一层都可能暂存数据 |
| 对应章节 | operating-systems/05-file-system.md、computer-organization/08-bus-io.md |
反思问题:
- 为什么崩溃一致性需要额外设计?
- 原子替换和日志分别解决什么?
- 什么时候必须关心刷盘?
Lab 11:网络请求阶段拆分
| 项目 | 内容 |
|---|---|
| 目标 | 把“请求慢”拆成阶段 |
| 观察点 | DNS、TCP、TLS、HTTP、下载可能分别变慢 |
| 操作 | 对一次 HTTPS 请求画阶段时间线 |
| 预期现象 | 慢点落在哪一段,排障方向不同 |
| 对应章节 | computer-networks/00-from-zero.md、computer-networks/12-network-troubleshooting.md |
反思问题:
- connect timeout 和 read timeout 区别是什么?
- TLS 失败为什么不一定有应用日志?
- 502/504 为什么可能来自网关而不是应用?
Lab 12:TCP 字节流没有消息边界
| 项目 | 内容 |
|---|---|
| 目标 | 理解 TCP 是字节流 |
| 观察点 | 多次 write 不等于多次 read |
| 操作 | 设计一个协议,如果没有长度字段或分隔符,接收方如何判断消息结束 |
| 预期现象 | 应用必须自己定义消息边界 |
| 对应章节 | computer-networks/06-tcp-basics.md |
反思问题:
- 粘包和拆包为什么不是 TCP bug?
- 固定长度、分隔符、长度字段各有什么问题?
- 为什么 HTTP 需要 Content-Length 或 chunked?
Lab 13:DNS TTL 变更延迟
| 项目 | 内容 |
|---|---|
| 目标 | 理解 DNS 缓存和 TTL |
| 观察点 | 权威记录变化不代表所有客户端立即变化 |
| 操作 | 假设旧记录 TTL 为 24 小时,画出不同解析器缓存过期时间 |
| 预期现象 | 一段时间内新旧结果共存 |
| 对应章节 | computer-networks/08-dns.md |
反思问题:
- 为什么重大切换前要提前降低 TTL?
- 为什么程序环境和手动测试环境解析结果可能不同?
- DNS 负载均衡为什么不是实时健康调度?
Lab 14:证据链复盘
| 项目 | 内容 |
|---|---|
| 目标 | 训练从症状到证据的排障方法 |
| 观察点 | 每个结论都必须有证据支持 |
| 操作 | 从 practical-casebook.md 任选一个 case,写出失败边界、路径图、证据、假设、验证方式 |
| 预期现象 | 模糊问题变成可推进的问题 |
| 对应章节 | foundations/09-debugging-observability.md |
反思问题:
- 我有没有把猜测当结论?
- 哪个证据能推翻我的假设?
- 修复后怎样证明真的解决了原问题?
练习顺序建议
| 阶段 | 练习 |
|---|---|
| 表示与计算 | Lab 01、02、03 |
| 性能直觉 | Lab 04、05、06、07 |
| OS 与并发 | Lab 08、09、10 |
| 网络与排障 | Lab 11、12、13、14 |
真正学会四大件,不是能复述定义,而是能通过这些练习把现象、机制和证据连起来。