进程互斥的软件、硬件实现方法,什么是互斥锁?

如需了解进程相关的详细概念,可以移步到我的另一个博客。
操作系统之进程管理
在这里插入图片描述

进程的互斥

进程的互斥是指在并发编程中,确保多个进程或线程在同一时间内不能同时访问共享资源或临界区。互斥的主要目的是避免竞态条件,从而保证数据的一致性和系统的稳定性。

还是上面的装箱的例子,箱子就是共享资源,两个人不能同时对箱子进行操作。

我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于临界资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。

进程互斥的软件实现方法

单标志法

设置一个标识,将首次执行的权利给其中一个进程,当这个进程执行完后,将此标识设置为下一个进程可以执行的标识。

标识

int turn = 0; // turn表示当前允许进入临界区的进程号

P0进程

while(turn != 0);  // 进入区
critical section;  // 临界区
turn = 1;					 // 退出区 
remainder section; // 剩余区 

P1进程

while(turn != 1);  // 进入区
critical section;  // 临界区
turn = 0;					 // 退出区 
remainder section; // 剩余区 

优点:实现简单;

缺点:只能按照p0->p1->p0->p…这种顺序执行,如果p0进程程将执行权让出去后,p1进程没有运行,p0线程就会一直处于等待状态。因此,单标志法存在的问题是:违背“空闲让进”原则。

双标志先检查

设置一个bool数组类型的标识。当一个进程想要执行的时候,先检查另一个进程有没有想要执行。如果另一个进程没有想要执行(即false),就可以将自己的标识设置为想要执行的状态(即true),执行完成后将自己的状态设置为不想再执行。如果另一个进程想要执行,就等待。

标识

bool flag[2];
flag[0] = false;
flag[1] = false;

P0进程

while(flag[1]);		 //① 先看看P1进程有没有访问临界区的意愿,如果有,就等待。
flag[0] = true;		 //② 标记为本进程
critical section;  //③ 临界区
flag[0] = false;	 //④ 退出区 
remainder section; // 剩余区 

P1进程

while(flag[0]);		 //⑤ 先看看P0进程有没有访问临界区的意愿,如果有,就等待。
flag[1] = true;		 //⑥ 标记为本进程
critical section;  //⑦ 临界区
flag[1] = false;   //⑧ 退出区
remainder section; // 剩余区 

优点:能够避免“空闲让进”的原则。

缺点:存在并发问题,如果程序按照①⑤②⑥的顺序执行的话,两个进程就会同时进入临界区。

双标志后检查

标识

bool flag[2];
flag[0] = false;
flag[1] = false;

P0进程

flag[0] = true;		 //① 先看看P1进程有没有访问临界区的意愿,如果有,就等待。
while(flag[1]);		 //② 标记为本进程
critical section;  //③ 临界区
flag[0] = false;	 //④ 退出区 
remainder section; // 剩余区 

P1进程

flag[1] = true;		 //⑤ 先看看P0进程有没有访问临界区的意愿,如果有,就等待。
while(flag[0]);		 //⑥ 标记为本进程
critical section;  //⑦ 临界区
flag[1] = false;   //⑧ 退出区
remainder section; // 剩余区 

优点:能够避免“空闲让进”与同时进入临界区的情况。

缺点:存在并发问题,如果程序按照①⑤②⑥的顺序执行的话,就会出现死锁的情况。

Peterson 算法

结合双标志法和单标志法的算法。

  1. 进程先表示自己想要进入临界资源
  2. 进程表示谦让,将执行权赋予另一个进程。
  3. 判断另一个进程是否想要进入临界资源,且执行权是不是在另一个进程,如果是,就等待;如果不是,就访问临界资源
  4. 访问临界资源
  5. 退出临界区

标识

bool flag[2];
flag[0] = false;
flag[1] = false;
int turn = 0;

P0进程

flag[0] = true;		 							 //① 表示自己想要执行
turn = 1;												 //② 将执行权先转让给P1进程
while(flag[1] && turn == 1);		 //③ 判断另一个进程有没有在执行
critical section; 							 //④ 临界区
flag[0] = false;								 //⑤ 退出区 
remainder section; 							 // 剩余区 

P1进程

flag[1] = true;		 							 //⑥ 表示自己想要执行
turn = 0;												 //⑦ 将执行权先转让给P0进程
while(flag[0] && turn == 0);		 //⑧ 判断另一个进程有没有在执行
critical section;  							 //⑨ 临界区
flag[1] = false;   							 //⑩ 退出区
remainder section; 							 // 剩余区 

按照①⑥②⑦③⑧的顺序执行,

  1. P0表示自己想要执行
  2. P1表示自己想要执行
  3. P0先谦让
  4. P1后谦让
  5. P0检查到P1想要执行单执行权并没有在P1,自己就执行了
  6. P1检查到执且P0想要执行且执行权在P0,就只能等待

进程互斥的硬件实现方法

  1. 中断屏蔽(Disable Interrupts)

    在单处理器系统中,可以通过在临界区代码运行之前禁用中断来实现互斥。这种方法简单有效,但是不适用于多处理器系统,因为它只能影响单处理器。

  2. 测试并设置(Test And Set):有的地方也称之为TSL(Test And Set Lock)。

    这是一种硬件提供的原子性操作,常常用于实现互斥锁,它可以在一条指令内测试一个标志位的值并将其设置为一个新值。如果一个测试的标志位为空闲状态,则可以将其值设置为1,并且进程可以进入临界区,否则进程将被阻塞等待。

    这种算法的容易导致忙等待,当进程持有锁的时候会持续占用CPU执行时间。

    以下是C语言的模拟逻辑:

    bool TestAndSwap(bool *lock){
      bool old = *lock;		// 先将旧值存储
      *lock = true;				// 在将当前锁设置为已上锁状态
      return old;					// 返回旧值
    }
    

    使用该指令的逻辑:

    // 锁
    bool lock = false;
    // 假设此方法会有多个线程调用
    void test(){
      while(TestAndSwap(&lock));
      	临界区代码... // 访问临界资源
        lock = false;// 解锁
      	其他代码...   // 临界区外的代码
    }
    
  3. 交换指令(Swap):

    有的地方也叫 Exchange 指令,或简称 XCHG 指令。

    Swap 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用C语言描述的逻辑

    Swap指令

    void Swap(bool *a ,bool *b){
      bool tmp = *a;
      *a = *b;
      *b =tmp;
    }
    

    使用代码:

    
    bool lock = false;
    // 假设此方法有多个线程调用
    void test(){
      bool old = true;
      while(old == true){
        Swap(&old,&lock)
      }
      临界区代码...		 // 访问临界区代码
      lock = false;   // 解锁
      临界区外的代码... // 访问临界区外的代码
    }
    

逻辑上来看 swap 和 TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在 old 变量上),再将上锁标记 lock 设置为 true,最后检査 old,如果 old 为false,则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞:适用于多处理机环境

缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用CPU并循环执行TSL指令,从而导致“忙等”。

互斥锁

概念

解决临界区最简单的工具就是互斥锁(mutexlock)。一个进程在进入临界区时应获得锁;在退出临界区时释放锁。函数 acquire()获得锁,而函数 release()释放锁。
每个互斥锁有一个布尔变量 available,表示锁是否可用。如果锁是可用的,调用 acquire()会成功,且锁不再可用。当一个进程试图获取不可用的锁时,会被阻塞,直到锁被释放。

acquire函数

void acquire(){
  while(!available){}
  available = false;
}

release函数

void release(){
  available = true;
}

acquire()或 release()的执行必须是原子操作,因此互斥锁通常采用硬件机制来实现。

互斥锁的主要缺点是忙等待,当有一个进程在临界区中,任何其他进程在进入临界区时必须连续循环调用 acquire()。当多个进程共享同一 CPU时,就浪费了 CPU周期。因此,互斥锁通常用于多处理器系统,一个线程可以在一个处理器上等待,不影响其他线程的执行。

需要连续循环忙等的互斥锁,都可称为自旋锁(spinlock),如TSL指令、swap指令、单标志法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值