首先介绍一个基础概念,
-
临界区:
临界区是指进程中的一段需要访问共享资源并且当另一个进程处于相对应代码区域时便不会被执行的代码。
-
互斥:
当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并访问任何相同的共享资源。 -
死锁
两个或以上的进程,在相互等待完成特定任务,而最终没法将自身任务进行下去。 -
饥饿
一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行但不被执行。
临界区
临界区的一些属性:
- 互斥:同一时间临界区内最多存在一个线程
- progress(前进):进入临界区的线程,最终一定会成功
- 有限等待:如果一个线程处于入口处,那么在i的请求被接受前,其他线程进入临界区的时间是有限制的。
- 无忙等待(可选):如果一个进程在等待进入临界区,那么在他额可以进入钱会被挂起。
保护方法:
硬件层次(中断)
-
优点
如果没有中断的话,就没有上下文切换,也就没有并发,所以就可以保证临界区的安全性。进入临界区,关闭中断,离开临界区,开启中断。 -
缺陷
禁用中断后,线程无法停止,其他线程陷入饥饿状态,无法确定响应这次中断需要多长时间。
软件实现互斥
单标志法
核心思想:设置一个公共整形变量turn,用于指示被允许进入临界区的进程编号。若turn = 0, 表示允许P0进入临界区。
循环是while(turn != i); 这种情况下,满足了互斥的条件,这时肯定只有一个线程符合条件进入,但这种情况无法满足前进的条件。因为无法判断其他代码是否想要进入。
这种情况无法满足互斥的情况,因为最开始的时候都是0,所以都可以进去。
这种情况满足了互斥,但是可能两个都被while循环挡住。
第三种结合两种方法:
flag表示想要进入,ture表示轮到谁进入临界区。
完整代码。
peterson算法:
更高层次的抽象:
利用系统的一些原语操作:
利用testandSet实现一个自旋锁:
如果希望不进行忙等的话,可以让线程进入睡眠。
基于原子操作的操作更简单,而且很容易实现n个进程的同步,支持多临界区。
缺点:
忙等消耗处理器时间
可能导致饥饿(因为一直抢不到)
拥有临界区的低优先级和获取处理器的高优先级导致死锁。