Linux 资源互斥(同步)方法 --锁和信号量

本文探讨了在Linux环境下,多线程共享资源时的互斥同步问题,包括睡眠锁和非睡眠锁的工作原理及选择策略。同时,分析了死锁现象及其避免方法,并讨论了中断处理中锁的特殊处理。

为什么要有互斥(同步):多个线程共享资源时,需要确保每个线程看到一致的数据视图,否则可能产生不可预知的结果。

常见的互斥机制如表中所示

名称类型简述优缺点使用场景
原子操作非睡眠锁原子操作可以保证指令以原子的方式执行--执行过程不被打断,因此以原子操作的共享资源就不存在一致性问题。被操作的资源一般为4字节或者8字节的资源,一般用于对计数器进行保护。

优点:使用简单,指令以原子方式操作。操作资源时,不存在阻塞和自旋的状态。一种非常轻量级的互斥方法。常常用于实现计数器(使用其他互斥方案来包含计数器,显得杀鸡用了宰牛刀)

缺点:不适用于共享资源比较复杂的场景。

常用于对简单的全局变量进行保护(如:计数器)。

自旋锁非睡眠锁在thread1在获取锁时,如果锁已经被占用,则thread1进行自旋,此时CPU空转,白白占用CPU。

优点:获取不到锁时,任务自旋,不会睡眠,因此可以用在哪些不能睡眠的任务中(中断服务程序)。

缺点:获取不到锁时,任务自旋,浪费CPU资源。

被保护的资源操作简单时,即持有锁的时间比较短的情景。

读写

自旋锁

非睡眠锁

读写自旋锁在加锁前有三种状态,在这三种状态下加锁会有不同的结果:

1、写加锁状态:资源已经被thread1加了写锁,此时无论thread2是以写方式还是读方式加锁,则失败,thread2进入自旋。

2、读状态加锁:资源已经被thread1加了读锁,此时thread2试图以读锁的方式再次加锁时,是能够成功获取到锁的。但当thread2以写方式获取锁,则失败,thread2自旋。

3、不加锁状态:没有thread对资源进行过加锁,此时无论thread1对资源进行加写锁或者读锁时,都能成功。

优点:当有多个任务读共享资源时,但只有比较少的任务写共享资源时,读写锁能够减少读任务自旋的次数,使得读任务能够更及时的执行。且能在中断服务程序中使用。

缺点:使用稍显复杂,在获取不到锁时,任务自旋,浪费CPU资源。

多个任务需读资源,较少的任务写资源,且持有锁的时间比较短。

互斥体睡眠锁当thread1已经对资源加锁,此时thread2再次获取资源锁时,thread2获取锁失败,thread2睡眠,当thread1释放资源锁后,重新触发调度,thread2就能成功获取锁。

优点:获取不到锁时,任务睡眠,等待锁可用时,再次唤醒任务,节约了CPU资源。

缺点:由于获取不到锁时,任务会睡眠,因此互斥体不能用于不能睡眠的任务中(中断)。

对被保护的资源进行比较复杂的操作,即持有锁的时间比较长。互斥体是对二值信号量的一种简化,一般来讲,使用互斥体就能满足要求时,则不推荐使用信号量。

信号量睡眠锁如果一个任务试图获得一个不可用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。当持有的锁被释放后,处于等待队列的那个任务将被唤醒,并获得信号量。

优点:获取不到锁时,任务睡眠,等待锁可用时,再次唤醒任务,节约了CPU资源。

缺点:使用稍显复杂,只有二值信号量才和锁的概念相近。

由于获取不到锁时,任务会睡眠,因此互斥体不能用于不能睡眠的任务中(中断)。

对被保护的资源进行比较复杂的操作,即持有锁的时间比较长。

睡眠锁和非睡眠锁:

睡眠锁:当thread获取不到锁时,thread进入睡眠,触发调度程序,切换到其他thread,当锁可用时,重新触发调度程序,thread1投入运行,thread1获取锁成功。也就是在锁变得可用时,thread1成功获取到锁时,最少要经过两次的上下文切换,而上下文切换也是占用CPU资源的。

非睡眠锁:当thread获取不到锁时,thread进入自旋,知道锁变得可用,也就是在此期间,CPU一直空转。

那么在可以睡眠的流程中,我们应该怎么选择使用的锁呢?我们已经知道,睡眠锁和非睡眠锁,在锁变得可用前,都是会耗费CPU资源的,睡眠锁是两次的上下文切换浪费CPU,非睡眠锁是自旋浪费时间,因此当我们认为锁在重新变得可用时,那个浪费的时间少,我们就选择哪个锁。所以,一般来讲,对那些共享资源操作较少的任务中,我们选择非睡眠锁,否则,选择睡眠锁。

死锁问题:
1、同一个线程试图对已经加锁的资源进行二次加锁。
2、一个线程在退出时未释放锁,另一个线程在对该锁进行加锁时。
3、两个线程A和B,A对x资源进行了加锁,B对y资源进行了加锁。此时A再想对y加锁时会被阻塞,B想对x进行加锁时会被阻塞。
   这样A永远获取不到x资源的锁,B永远获取不到y资源的锁。

如何避免死锁。
1、一个线程中,避免对已经加锁的资源再次加锁。
2、在线程退出时,一定要释放自身持有的锁。
3、多个线程和多个锁时,必须保证各线程加锁的顺序保持一致。例如,有A和B两个线程,A会操作x,y,z三个资源所,B会操作
y和z两个资源所。如果A加锁的顺序是x,y,z,则B加锁的顺序必须是y,z。如果A加锁的顺序是x,z,y,则B加锁的顺序必须是z,y

中断的锁处理:

中断中无法使用睡眠锁,且使用非睡眠锁也有限制。在中断处理程序中,自旋锁在使用前一定要禁止本地中断,然后再去获取指定的锁。
考虑如下例程:
1、A中断处理程序已经持有锁,并在操作共享资源。
2、来了新的高优先级的中断B,此中断打断了前一个中断,并申请锁。
3、由于A中断已经持有锁,且未释放,因此B中断无法申请到锁,于是只能自旋。
4、由于中断服务程序未完成前,是不会进行调度的,B只能一直自旋,CPU一直在B上空转。

中断中为什么无法使用睡眠锁:

因为调度程序是无法调度中断的,一旦中断因为获取不到锁进入了睡眠,则触发调度程序,CPU切换到其他任务中,则再也无法切换到中断服务任务中,睡眠后续的中断流程无法得到执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值