避免死锁
16.4 ID_deadlock
对于锁等资源,错误的请求时序或管理方式会使程序永远陷入等待状态,这种问题称为“死锁(deadlock)”。
示例:
mtx_t m; // Non-recursive mutex
void foo() {
mtx_lock(&m); // Lock the mutex
....
}
void bar() {
mtx_lock(&m); // Lock the mutex
foo(); // Undefined behavior, may deadlock
....
}
设 m 是非递归互斥量,bar 锁定互斥量后调用 foo,而 foo 也会锁定互斥量,导致 foo 等待 bar 解锁,而 foo 返回之前 bar 不可能解锁,这是一种导致死锁的逻辑错误,C11 也明确规定在同一线程中不可重复锁定非递归互斥量。
另外,线程之间相互等待对方解锁也是死锁的主要原因,如:
struct A {
....
mtx_t m; // Mutex
} a, b;
void thr1() {
mtx_lock(&a.m); // Lock
mtx_lock(&b.m);
....
}
void thr2() {
mtx_lock(&b.m); // Lock in another order
mtx_lock(&a.m); // May deadlock
....
}
设 thr1 和 thr2 是两个可以并发执行的函数,如果 a.m 被 thr1 锁定,b.m 被 thr2 锁定,thr1 等待 b.m 解锁,而 thr2 等待 a.m 解锁,这种相互等待导致了死锁的局面。例中 a 和 b 是具名全局对象,在各线程中按统一的顺序加锁可避免死锁。
在更普遍的情况下,为不同对象加锁前,可使对象按某种内在的标准“排序”,再依次加锁,如:
struct A {
int id; // Unique identifier
....
mtx_t m; // Mutex
};
void lock_in_order(A* p, A* q) {
if (p->id > q->id) {
A* t = p; p = q; q = t;
}
mtx_lock(&p->m);
mtx_lock(&q->m);
}
为每个对象分配一个 id 以标识不同的对象,每次 id 小的先加锁,可有效避免相互等待造成的死锁。示例代码忽略了 id 相等的情况,在实际代码中应补全,否则也会造成第一个例子中的问题。
依据
ISO/IEC 9899:2011 7.26.4.3(2)-undefined
ISO/IEC 14882:2011 17.3.8