Day 054 — 并发入门:线程 — 练习清单
✅ 今日完成清单
- [ ] 理解 GIL 的原理和影响
- [ ] 掌握 threading 模块创建线程
- [ ] 理解守护线程 vs 普通线程
- [ ] 掌握 Lock/RLock 的使用
- [ ] 理解竞态条件及其解决方案
- [ ] 了解 Condition/Semaphore/Event 的用法
- [ ] 完成实战:并发下载器
- [ ] 完成以下练习题
📝 基础练习题
练习 1:多线程累加器
创建一个程序,使用 10 个线程同时累加到一个共享变量,每个线程累加 100,000 次。分别用不加锁和加锁的方式实现,对比结果。
要求: - 不加锁版本结果不稳定 - 加锁版本结果始终正确 - 打印两种方式的对比
练习 2:线程安全的栈
实现一个线程安全的栈类 ThreadSafeStack,支持以下操作:
- push(item) — 入栈
- pop() — 出栈(栈空时返回 None)
- peek() — 查看栈顶元素
- size() — 获取大小
要求: - 所有操作必须线程安全 - 编写测试代码验证并发安全
练习 3:并行文件处理
编写一个程序,用多线程读取一个目录下的所有 .txt 文件,统计每个文件的行数和总字数。
要求:
- 使用 ThreadPoolExecutor
- 每个文件单独一个线程处理
- 汇总结果并打印
🚀 进阶挑战题
挑战 1:生产者-消费者带背压
实现一个生产者-消费者模型: - 生产者产生 0-99 的数字 - 消费者计算数字的平方并存储 - 缓冲区最大容量为 5 - 当缓冲区满时,生产者等待 - 当缓冲区空时,消费者等待
进阶要求: - 实现多个生产者和多个消费者 - 统计每个消费者处理了多少个元素
挑战 2:读写锁
实现一个读写锁(Read-Write Lock): - 多个线程可以同时读取 - 写入时不允许其他线程读写 - 写入优先(防止写饥饿)
提示:
- 使用 threading.Condition
- 维护读取计数器和写入标志
挑战 3:线程池监控器
扩展 ConcurrentDownloader 类,添加以下功能:
- 实时显示下载速度(bytes/s)
- 支持取消未开始的任务
- 下载失败时自动重试(最多 3 次)
- 生成 JSON 格式的下载报告
💡 思考题
-
为什么
threading.Thread的args参数必须是元组?args=(1)和args=(1,)有什么区别? -
在什么场景下,多线程程序的性能可能比单线程更差?举出至少 2 个例子。
-
threading.local()的作用是什么?它和全局变量有什么区别?在什么场景下使用?