<aside> 📖 基本思路:自我否定
始终假设自己的代码是错的
</aside>
<aside> 📖 Bug 多的根本原因:编程语言的缺陷
软件是需求 (规约) 在计算机数字世界的投影。
编程语言只管 “翻译” 代码,不管和实际需求 (规约) 是否匹配
</aside>
<aside> 📖 解决方法:防御性编程
把程序需要的条件用 assert
表示出来,即根据现实需求定义自己的规约。
assert
的逻辑本身也可能犯错,但 assert
和代码本身加起来是对逻辑正确性的双重保障。
jyy: 在面试时很有用
</aside>
<aside> 📖 死锁 Deadlock
A deadlock is a state in which each member of a group is waiting for another member, including itself, to take action.
</aside>
<aside> 📖 死锁产生的四个必要条件 (Edward G. Coffman, 1971)
假设你的 spinlock 不小心发生了中断
<aside> 📖 避免死锁
利用防御性编程检测,及早报告,及早修复
</aside>
void os_run() {
spin_lock(&list_lock);
spin_lock(&xxx);
spin_unlock(&xxx); // ---------+
} // |
// |
void on_interrupt() { // |
spin_lock(&list_lock); // <--+
spin_unlock(&list_lock);
}
上锁的顺序很重要
<aside> 📖 避免死锁 Lock Ordering
任意时刻系统中的锁都是有限的
严格按照固定的顺序获得所有锁 (lock ordering; 消除 “循环等待”)
“在任意时刻总是有获得 “最靠后” 锁的可以继续执行”
</aside>
void swap(int i, int j) {
spin_lock(&lock[i]);
spin_lock(&lock[j]);
arr[i] = NULL;
arr[j] = arr[i];
spin_unlock(&lock[j]);
spin_unlock(&lock[i]);
}
<aside> 📖 数据竞争
不同的线程同时访问同一段内存,且至少有一个是写。
</aside>
<aside> 📖 问题
不上锁就不会产生死锁问题,但数据竞争问题会随之产生
</aside>
<aside> 📖 解决方案
无锁的并发程序(Peterson算法)对于大型项目难以实现
因此最好的方法是用互斥锁保护好共享数据,消灭一切数据竞争
但也会有其他问题:
// Case #1: 上错了锁
void thread1() { spin_lock(&lk1); sum++; spin_unlock(&lk1); }
void thread2() { spin_lock(&lk2); sum++; spin_unlock(&lk2); }
// Case #2: 忘记上锁
void thread1() { spin_lock(&lk1); sum++; spin_unlock(&lk1); }
void thread2() { sum++; }
</aside>