临界区:访问和操作共享数据的代码段
如果两个执行线程有可能处于同一个临界区中同时执行,那么就称其为竞争条件
避免并发和防止竞争条件称为同步。
加锁
锁是采用原子操作实现的,而原子操作不存在竞争。
造成并发执行的原因:
- 用户空间:
- 用户程序会被调度程序抢占和重新调度
- 信号处理是异步发生的
- 内核空间:
- 中断
- 软中断和tasklet
- 内核抢占
- 睡眠及与用户空间的同步
- 对称多处理
真正用锁来保护共享资源并不困难,但辨认真正需要共享的数据和相应的临界区,才是真正有挑战的地方。因此,在编码的开始阶段就要设计恰当的锁。大多数内核数据结构都需要加锁。如果有其他执行线程可以访问数据,那么就给这些数据加上某种形式的锁;如果任何其他什么东西都能看到数据,那么就要锁住它。注意:要给数据而不是代码加锁。
- 中断安全代码:在中断处理程序中能避免并发访问的安全代码
- SMP安全代码:在对称多处理的机器中能避免并发访问的安全代码
- 抢占安全代码:在内核抢占时能避免并发访问的安全代码
死锁
死锁的产生需要一定条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中一个资源,但所有资源都已经被占用了。所有线程都在互相等待,但永远不会释放已经占有的资源。于是所有线程都无法继续,发生了死锁。
避免死锁的一些简单规则:
- 按顺序加锁
- 防止发生饥饿
- 不要重复请求同一个锁
- 设计应力求简单
争用和扩展性
加锁粒度用来描述加锁保护的数据规模。
锁的争用(lock contention),简称争用,是指当锁正在被占用时,有其他线程试图获得该锁。
当锁争用严重时,加锁太粗会降低可扩展性;而锁争用不明显时,加锁过细会加大系统开销,带来浪费。这两种情况都会造成系统性能下降。