【操作系统】互斥锁与信号量

目录

1、互斥锁(mutex)

2、信号量

3、两者区别


1、互斥锁(mutex)

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。注意:同一时刻,只能有一个线程持有该锁。

当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,直接访问该全局变量依然能够访问,但会出现数据混乱。

所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但并没有强制限定。因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 

1)互斥锁初始化

  • 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 变量0和1

  • 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL)

2)加锁与解锁

  • lock与unlock:

lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。

unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。

可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++

  • lock与trylock:

lock加锁失败会阻塞,等待锁释放。

trylock加锁失败直接返回错误号(如:EBUSY),不阻塞

2、信号量

1)信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表等待使用该资源的进程个数; 信号量S的值仅能由PV操作(原子操作)来改变。

2)类别

整型信号量:整形信号量表示的是资源的数目S。P操作将减少一个资源,V操作将增加一个资源;

在整型信号量中,只要S <= 0,就会不断测试,看何时有资源可用。也因此进程会一直等,不遵循让权等待的原则。更好的做法是,加一个就绪队列。故引入记录型信号量。

记录型信号量

s.value大于0时,值表示的是等待进程的数目;s.value小于等于0时,绝对值表示的是等待进程的数目。

链表L表示的是就绪队列,因此核心仍是PV,只不过如何对待进程,记录型信号量的做法更加完善。解决了让进程忙等的现象。

typedef struct 
{
    int value;  //与上S值意义相同,表可用的资源数目
    struct process *L;  //L表就绪队列
} semaphore;
void P(semaphore s)
{
    s.value--; //申请资源
    if(s.value < 0)  //若边界value原先为0,那么现在变-1,表示无资源,该进程需要进入就绪队列等待
    {
        将此进程加入就绪队列,等待;
        block(s.L);
    }
}
​
void V(semaphore s)
{
    s.value++;  //释放资源
    if(s.value <= 0)   //当value为0时,是从value为-1变过来的,因此,虽然value = 0了,还不能忘了把最后一个进程移出就绪队列并叫醒它。
    {
        将进程P从就绪队列中移出;
        wakeup(P);// 叫醒P,让它起来干活
    }
}
​
//对比
void V(semaphore s)
{
    if(s.value < 0)
    {
        将进程P从就绪队列中移出;
        wakeup(P);// 叫醒P,让它起来干活
    }
    s.value++;
}

3)“挂起等待”和“唤醒等待线程”的操作

每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先在把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。

4)使用PV操作实现进程互斥时应该注意的是:

  • 每个程序中用户实现互斥的P、V操作必须成对出现,先做P操作,进临界区,后做V操作,出临界区。若有多个分支,要认真检查其成对性。

  • P、V操作应分别紧靠临界区的头尾部,临界区的代码应尽可能短,不能有死循环。

  • 互斥信号量的初值一般为1。

5)三个典型同步问题

生产者与消费者问题

作者读者问题

哲学家进餐问题

3、两者区别

1)、互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2)、互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为1时,也可以完成一个资源的互斥访问。

3)、互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

4)互斥锁和二元信号量在使用上非常相似,但是互斥锁解决了优先级翻转的问题

优先级翻转

1、优先级翻转问题(Priority Inversion)即当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,而这个低优先级任务在访问共享资源时可能又被其它一些中等优先级任务抢先,因此造成高优先级任务被许多具有较低优先级任务阻塞,实时性难以得到保证。

例如:有优先级为A、B和C三个任务,优先级A>B>C,任务A,B处于挂起状态,等待某一事件发生,任务C正在运行,此时任务C开始使用某一共享资源S。在使用中,任务A等待事件到来,任务A转为就绪态,因为它比任务C优先级高,所以立即执行。当任务A要使用共享资源S时,由于其正在被任务C使用,因此任务A被挂起,任务C开始运行。如果此时任务B等待事件到来,则任务B转为就绪态。由于任务B优先级比任务C高,因此任务B开始运行(不需要使用共享资源S,不阻塞),直到其运行完毕,任务C才开始运行。直到任务C释放共享资源S后,任务A才得以执行。在这种情况下,优先级发生了翻转,任务B(中等优先级)先于任务A(高级优先级)运行

=> 高级优先级A原先因共享资源S只需要等待低等优先级C,但现在还需要等待不需要共享资源S的中等优先级B先运行完,才能运行

2、解决办法

1)优先级天花板(Priority Ceiling)

优先级天花板是当任务申请某资源时, 把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级, 这个优先级称为该资源的优先级天花板。这种方法简单易行, 不必进行复杂的判断, 不管任务是否阻塞了高优先级任务的运行, 只要任务访问共享资源都会提升任务的优先级。

2)优先级继承(Priority Inheritance)

优先级继承是当任务A 申请共享资源S 时, 如果S正在被任务C 使用,通过比较任务C与自身的优先级,如发现任务C 的优先级小于自身的优先级, 则将任务C的优先级提升到自身的优先级, 任务C 释放资源S 后,再恢复任务C 的原优先级。这种方法只在占有资源的低优先级任务阻塞了高优先级任务时才动态的改变任务的优先级,如果过程较复杂, 则需要进行判断。

互斥锁采用了优先级继承的方法解决了优先级翻转问题,但是信号量没有;

3、注意点:

1)优先级反转提醒我们使用锁的代码段应尽量短;

2)注意用小锁代替大锁,减少冲突的机会;

3)如果锁保护的代码段很短,直接使用原子锁忙等也是不错的一个方法。

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值