0%

锁使用的一些笔记

使用的场景

当一个数据结构有多个并发的流程去访问的时候,可以加锁去做互斥,这样数据的一致性的
到保护。并发流程有多种表现形式,比如在linux内核里,内核线程,中断,来自用户态的
系统调用等都可以并发起来; 用户态的话, 各个线程,信号(信号处理函数里不能使用锁)可
以并发。

加锁其实就是多个执行流在临界区外排队(这里不考虑try锁,就是那种试一下可以加上就加
锁,不可以加上当场就返回的锁)。我们也可以给临界区配置一个原子变量标记,每个执行流
在先抢到这个标记才可以操作保护的数据结构, 操作完保护的数据结构,退出临界区的时候
释放这个标记。原子变量标记的方式,其实是用try锁去保护相应的数据结构。但是try锁没
有了排队等待,需要在加锁失败时做必要的处理。

使用时注意的事项

锁使用时最需要注意的就是死锁,死锁的最典型方式是两把锁交叉加锁:

1
2
3
4
5
thread1        thread2

lock1 lock2
[...] [...]
lock2 lock1

如上,thread1加了lock1,执行下面的代码,thead2加了lock2。这时, thread1想要加lock2,
但是加不上,thread2想要加lock1的,但是也加不上。

同一个执行流重复加一把锁也会带来死锁:

1
2
3
4
5
thread1

lock1
lock1
[...]

使用锁的时候要注意锁的不同种类,一般有spinlock和mutex,他们都有对应的读写锁的版本。
一般情况我们可以先不用读写锁,直到确实是性能瓶颈, 我们才去做优化。spinlock是死循
环等待获取锁的,mutex在获取锁失败后会sleep, 直到可以得到锁。所以在不可以sleep的
场景里,我们要用spinlock锁, 和mutex比较,spinlock不sleep,所以,spinlock也适用于
临界区很短的加锁保护。

调试锁相关代码

一般我们写好Linux内核里锁相关的代码,可以打开内核里死锁检测:
Kernel hacking —> Debug Lockups and Hangs

更多死锁检测的介绍可以参考这里

死锁检测会检测出潜在的死锁位置,然后输出报告。hange检测在超出配置的超时时间后会
把调用栈打出来。不过这些检测打开后会对系统性能有较大的影响,这些配置只能在调试版
本里打开。

(to do: 用户态死锁检测的工具)

加锁后相关的性能问题

为了维护并发访问的资源,所以需要加锁。所以,加锁之后有可能带来的性能下降,本质上
是各个执行流相互等待带来的开销。所以,要提高性能,还是要把各个执行流的相互依赖解开。

加锁对构架演进的影响

锁临界区太大,会对后续添加新的锁进来产生影响,比较容易造成各种死锁问题。

锁的实现

Linux内核spinlock的实现逻辑分析可以参考这里