1、临界区(critical
regions)就是访问和操作共享数据的代码段。多个执行线程并发访问同一个资源通常是不安全的,为了避免在临界区中并发访问,编程者必须保证这些代码
原子地执行。也就是说,代码在执行结束前不可被打断,就如同整个临界区是一个不可分割的指令一样。
2、如果两个执行线程有可能处于同一个临界区中,我们就称他是竞争条件(race conditions)。避免并发和防止竞争条件被称为同步(synchronization)。
3、内核中可能造成并发执行的原因有以下几点:
- 中断-中断几乎可以在任何时刻异步发生,也就可能随时打断当前正在执行的代码。
- 内核抢占-因为内核具有抢占性,所以内核中的任务可能会被另一任务抢占。
- 睡眠及与用户空间的同步-在内核执行的进程可能会睡眠,这就会唤醒调度程序,从而导致调度一个新的用户进程执行。
- 对称多处理-两个或多个处理器可以同时执行代码。
4、如果在一段内核代码操作某资源的时候系统产生了一个中断,而且该中断的处理程序还要访问这个资源;如果一段内核代码在访问一个共享资源期间可以被抢占;如果内核代码在临界区里睡眠都会导致竞争条件,是一个Bug。
5、在编写代码的开始阶段就要设计恰当的锁,这是基本原则。如果代码已经写好了,再在其中找到需要上锁的部分并向其中追加锁,是非常困难的。
6、哪些数据需要加锁保护呢?有一条很好的经验可以帮助我们判断:如果有其他执行线程可以访问这些数据或任何其他什么东西能看到它,那么就给这些数据加上某种形式的锁。在编写内核代码时,你要问自己下面这些问题:
- 这个数据是不是全局的?除了当前线程外,其他线程能不能访问它?
- 这个数据会不会在进程上下文和中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
- 进程在访问数据时可不可能被抢占?被调度的新程序会不会访问同一数据?
- 当前进程是不是会睡眠(阻塞)在某些资源上,如果是,它会让共享数据处于何种状态?
- 怎样防止数据失控?
- 如果这个函数又在另一个处理器上被调度将会发生什么?
- 你要对这些代码做什么?
7、死锁的产生需要一定的条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个资源,但所有的资源都已经被占用了。所有线程
都在相互等待,但它们永远不会释放已经占有的资源。于是任何线程都无法继续,这便意味着死锁的发生。一些简单的规则对避免死锁大有帮助:
- 加锁的顺序是关键。使用嵌套的锁时必须保证以相同的顺序获取锁,这样可以阻止致命拥抱类型(每个线程都在等待其他
线程持有的锁,但绝没有一个线程会释放它们一开始就持有的锁)的死锁。最好能记录下锁的顺序,以便其他人也能照此顺序使用。尽管释放锁的顺序和死锁是无关
的,但最好还是以获得锁的相反顺序来释放锁。
- 防止发生饥饿。
- 不要重复请求同一个锁。
- 越复杂的加锁方案越有可能造成死锁-设计应力求简单。
8、锁的争用(lock contention)是指当锁正被占用时,有其他线程试图获得该锁。由于锁的作用是使程序以串行方式对资源进行访问,所以使用锁无疑会降低系统的性能。
9、扩展性(Scalability)是对系统可扩展程度的一个量度。加锁粒度用来描述加锁保护的数据规模。一般来说,提高可扩展性是件好事,但一
味的提高可扩展性,却会导致Linux在小型SMP和UP机器上性能的降低,这是因为小型机器可能用不到特别精细的锁,锁得过细只会增加复杂度,并加大开
销。
阅读(1522) | 评论(0) | 转发(0) |