from mosaic import *
OS2023(13)
13. 并发 Bug 的应对¶
Changelog & 反馈
- 的确是 Mandelbrot set (但选了一个比较特别的放大的区域)
背景回顾:我们在编写并发程序时,难免会遇到死锁、数据竞争、原子性/顺序违反等类型的并发 bugs。即便我们知道它们的定义和触发条件,直接在编程时消灭它们依然是十分困难的。以数据竞争为例,它的定义貌似简单:两个线程同时访问同一内存地址,并且至少有一个是写。但 “访问内存” 则可能出其不意——例如 ret 指令和栈上数据的修改产生的数据竞争。那么,我们应该如何应对这些并发 bugs?
本讲内容:一节真正的 “编程 ”课:如何正确地 (并发) 编程:
- Lock ordering
- 防御性编程
- 运行时检查
slideshow('13.1')
demo('lock-ordering', 'c/lock-ordering.c', libs=['thread.h', 'thread-sync.h'])
即便是最容易发现、最容易预防的死锁类 bug,在实际的复杂系统中,想要使程序员能够正确遵守编程规范,也是十分困难的。因此,对于复杂的系统,我们必须总是假设程序员会花式犯错,最终才能得到高质量的系统。
slideshow('13.2')
demo('spinlock', 'c/spinlock-xv6.c', libs=['thread.h', 'thread-sync.h'])
防御性编程对大型系统来说是至关重要的。如果没有适当的 assertions,调试代码会变得非常艰难。
slideshow('13.3')
demo('lockdep', 'c/lockdep.c', libs=['thread.h'])
demo('uaf', 'd/uaf.c')
demo('alipay', 'c/alipay.c', libs=['thread.h'])
slideshow('13.4')
Take-away Messages¶
Bugs (包括并发 bugs) 一直以来困扰着所有软件工程的实践者。这是因为到目前,仍然没有经济、可靠的手段能帮助我们检查实现的程序逻辑 (状态机描述,一个纯粹的数学对象) 是否满足我们设计时的种种预期。因此,为了实现 “更正确” 的软件,我们把对程序的预期表达在程序中 (race-free, lock ordering, ...),而不是让程序在自然状态下悄悄进入有问题的状态,就是我们目前解决程序调试问题的折中办法。
各类 sanitizer 给我们带来的启发是:如果我们能清楚地追溯到问题产生的本源,我们就总是能找到好的应对方法——甚至是我们可以构造低配版的 “近似” sanitizer,它们在暗中帮助你实现 fail-fast 的程序,从而减轻你调试问题的负担。
课后习题/编程作业¶
1. 阅读材料¶
教科书 Operating Systems: Three Easy Pieces:
- 第 32 章 - Concurrency Bugs
2. 编程实践¶
在实验中尽可能地增加运行时检查和压力测试,以帮助你诊断实验中可能发生的问题。