在多线程模型中,锁是一个复杂的东西,即便老司机也经常会翻车。
可见 nginx
和 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. 小结
- 在一个功能单元里,尽量不要调用有其它锁的函数。
- 锁的粒度尽量小,锁是锁数据的,不是锁逻辑。在一个函数里,函数入口加锁,函数退出解锁,这种操作看似方便,其实隐藏了很多问题。假如锁住了插入数据库语句逻辑,刚好这个数据库堵了,那么整个多线程系统有可能卡在这里。