(九)内核同步介绍
共享资源要防止并发访问,因为如果多个执行线程同时访问和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,造成被访问数据处于不一致状态。
临界区和竞争条件
所谓临界区(也称为临界段),是指访问和操作共享数据的代码段。为了避免在临界区中并发访问,必须保证这些代码原子地执行,即操作在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。
如果两个执行线程有可能处于同一个临界区中同时执行,那么这就是程序包含的一个bug,我们称它是竞争条件(这里会存在线程竞争)。
避免并发和防止竞争条件称为同步。
加锁
当共享资源是一个复杂的数据结构时,竞争条件往往会使该数据结构遭到破坏。我们需要一种方法确保一次有且只有一个线程对数据结构进行操作,或者当另一个线程在对临界区标记时,就禁止其他访问。
要给数据加锁而不是给代码加锁。
内核中有几类可能造成并发执行的原因:
- 中断:中断几乎可以在任何时刻异步发生,也就可能随时打断当前正在执行的代码。
- 软中断和tasklet:内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在执行的代码。
- 内核抢占:因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
- 睡眠及与用户空间的同步:在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行。
- 对称多处理:两个或多个处理器可以同时执行代码。
几乎访问所有的内核全局变量和共享数据都需要某种形式的同步方法。
死锁
所有线程都在相互等待,但它们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。
每个线程都在等待其他线程持有的锁,但是绝没有一个线程会释放它们一开始就持有的锁,所以没有任何锁会在释放后被其他线程使用。
为避免死锁可以设置一些简单的规则:按顺序加锁;防止发生饥饿;不要重复请求同一个锁;设计应力求简单,越复杂的加锁方案越有可能造成死锁。
争用和扩展性
锁的争用,是指当锁正在被占用时,有其他线程试图获得该锁。
被高度争用的锁会成为系统的瓶颈,严重降低系统性能。
当锁争用严重时,加锁太粗会降低可扩展性;而锁争用不明显时,加锁过细会加大系统开销,带来浪费,这两种情况都会造成系统性能下降。