☰
  • 首页
  • 规则分类
  • 项目介绍
search
•••

避免死锁

16.4 ID_deadlock
目录 › next › previous

对于锁等资源,错误的请求时序或管理方式会使程序永远陷入等待状态,这种问题称为“死锁(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

参考

SEI CERT CON35-C SEI CERT CON53-CPP SEI CERT CON56-CPP
Copyright©2024 360 Security Technology Inc., Licensed under the Apache-2.0 license.