一、中断禁用
单处理机:在单处理机中,并发进程不能重叠,只能交替,操作系统通过中断来实现进程的交替,那么就会存在一个问题,当一个进程A运行到临界区时发生了中断,另一个进程B运行,进程B也可以运行临界区代码,这样就改变了临界资源,当进程A再接着中断的位置运行时相关数据有可能已经被进程B修改,出现错误。
这个问题可以通过禁用中断来解决,禁用中断和启用中断都是原语操作。通过这个方法来实现互斥如下:
while(true)
{
/* 禁用中断 */
/* 临界区 */
/* 启用中断 */
/* 其余部分 */
}
中断禁用的优缺点:
优点:临界区不可被中断,故此可以保证互斥
缺点:由于处理器被限制于只能交替执行程序,因此执行的效率有明显的降低
二、专用机器指令
多处理机:在多处理机配置中,几个处理器共享内存,处理器间的行为是对等的,处理器之间没有支持互斥的中断机制。在硬件级别上,对存储单元的访问排斥对相同单元的其他访问。基于这一点,处理器的设计者提出了一些机器指令,用于保证两个动作的原子性,即某一时刻只能有一个临界区代码访问临界资源。两种最常见的指令是:比较和交换指令、交换指令。
- 比较和交换指令如下:
int compare_and_swap(int *word, int testval, int newval)
{
int oldval;
oldval = *word;
if(oldval == testval) *word = newval;
return oldval;
}
示例:
const int n = /* 进程个数 */
int bolt;
void p(int i)
{
while(true)
{
while(compare_and_swap(bolt, 0, 1) == 1)
{
/* 不做任何事 */
}
/* 临界区 */
bolt = 0;
/* 其余部分 */
}
}
void main()
{
bolt = 0;
parbegin (p(1), p(2), ... ,p(n));
}
进程开始,bolt = 0当某一个进程A率先执行比较和交换指令时可以通过,通过之后便将bolt改为1,而测试值是0,因此其他进程执行比较和交换指令时不能通过,处于忙等待状态(在进程得到临界区访问权之前,它只能继续执行测试变量的指令来得到访问权,除此之外不能做其他事情),当进程A执行完临界区之后又将bolt值改为0, 则将会有一个进程测试通过,往复执行,即可保证某一时刻只有一个临界区代码访问临界资源。
- 交换指令如下:
void exchange(int *register, int *memory)
{
int temp;
temp = *memory;
*memory = *register;
*register = temp;
}
示例:
int const n = /* 进程个数 */;
int bolt;
void p(int i)
{
int keyi = 1;
while(true)
{
do exchange(&keyi, &bolt)
while(keyi != 0)
{
/* 临界区 */
}
bolt = 0;
/* 其他部分 */
}
}
void main()
{
bolt = 0;
parbegin (p(1), p(2), ... ,p(n));
}
进程开始,bolt = 0当某一个进程A率先执行交换指令时可以通过,通过之后便将bolt改为1,因此其他进程执行交换指令时bolt = 1,通过交换将keyi置为1,则将一直处于while循环中,无法执行下边临界区。当进程A执行完临界区之后又将bolt值改为0, 则将会有一个进程测试通过,往复执行,即可保证某一时刻只有一个临界区代码访问临界资源。
机器指令方法的优缺点:
优点
- 适用于在单处理器或共享内存的多处理器上的任何数目的进程
- 非常简单且易于证明
- 可用于支持多个临界区,每个临界区可以用它自己的变量定义
缺点
- 使用了忙等待:因此当一个进程正在等待进入临界区时,它会继续消耗处理器时间
- 可能饥饿:当一个进程离开一个临界区并且有多个进程正在等待时,选择哪一个进程是任意的,因此某些进程可能被无限拒绝进入。
- 可能死锁:当某个进程通过专门指令时,在临界区中发生中断将有可能发生死锁。