剖析嵌套式死锁问题

2020-03-08

在多线程模型中,锁是一个复杂的东西,即便老司机也经常会翻车。

可见 nginxredis 为什么主逻辑都在单进程单线程里实现(redis 有多线程,但是主逻辑还是在主线程里实现的)。单线程最大的好处是避开了锁带来的复杂度。

锁是配对出现的。锁上了,就要解锁,忘记解锁会产生死锁,一般这种低级错误很容易避免,然而在复杂的业务体系中,多人共同开发,往往会产生嵌套式死锁问题,而这个问题藏得很深。


1. 嵌套死锁理解

嵌套式死锁:系统中存在多个锁,通过函数相互调用。

下面伪代码,展示两个线程同时运行,极有可能产生嵌套死锁问题。

多线程逻辑时序

设计图来源:《嵌套式死锁原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void func1() {
    lock1();
    ...
    func22();
    ...
    unlock1();
}

void func12() {
    lock1();
    ...
    unlock1();
}

void func2() {
    lock2();
    ...
    func12();
    ...
    unlock2();
}

void func22() {
    lock2();
    ...
    unlock2();
}

void thread1() {
    func1();
}

void thread2() {
    func2();
}

2. 小结

  • 在一个功能单元里,尽量不要调用有其它锁的函数。
  • 锁的粒度尽量小,锁是锁数据的,不是锁逻辑。在一个函数里,函数入口加锁,函数退出解锁,这种操作看似方便,其实隐藏了很多问题。假如锁住了插入数据库语句逻辑,刚好这个数据库堵了,那么整个多线程系统有可能卡在这里。