Linux 互斥:互斥量、死锁及死锁检测、读写锁、自旋锁

我们先来看看为什么需要同步和互斥呢?
因为需要保证线程安全,线程安全是指多个线程在同时运行时访问临界资源,不会导致程序产生二义性,程序结果唯一。但是多个线程在对临界资源进行的是非原子操作,有可能被其他线程打断,从而导致操作和得到结果不一致,进而产生程序的二义性。所以就需要同步与互斥来保证。

原子操作:操作一步完成,要么完成,要么没有完成,不存在中途被其他执行流打断。

互斥

功能:每一次只能有一个执行流访问临界资源,在这个执行流访问资源的期间,其他执行流不得访问资源。
互斥的实现
想实现互斥必须要用到互斥锁
互斥锁其实底层是一个互斥量,互斥量就是一个计数器。该计数器只有两个值0、1。(这个互斥变量是保存在寄存器中的)
0:表示当前不可以加锁,临界资源不可以被访问。
1:表示当前可以加锁,临界资源可以被访问。
也就是说当执行流想要访问临界资源时,需要先加锁,如果加锁成功那么访问临界资源。如果加锁失败,则阻塞知道加上锁。当执行流结束对临界资源的访问,就需要释放锁资源,以便让其他线程获取锁资源。

那么因为互斥量是一个计数器,所以在0或者1之间相互转换的时候,也是非原子操作的,有可能被其他执行流打断。 为了解决此问题我们是这样做的
在这里插入图片描述

互斥锁的相关函数
1.定义互斥锁变量
pthread_mutex_t

2.初始化互斥锁变量
动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr );
另一种初始化的方式(静态分配):
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER
PTHREAD_MUTEX_INITIALIZER:是一个宏定义,里面对结构体进行了宏定义,使用这种方法初始化的时候,其实是将结构体中的变量赋值给互斥锁变量。

3.加锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:
如果临界资源可用,也就是互斥锁变量计数器为1,加锁成功,执行流继续执行下去。
如果临界资源不可用,互斥锁变量计数器为0,加锁失败,执行流阻塞等待,直到加上锁。

int pthread_mutex_trylock(pthread_mutex_t *mutex)
功能:
如果临界资源可用,也就是互斥锁变量计数器为1,加锁成功,执行流继续执行下去。
如果临界资源不可用,不会进行阻塞等待,加锁失败,返回EBUSY。

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abs_timeout);
abs_timeou:加锁超时时间。
功能:
当超过加锁超时时间还没有获得锁资源时,就会报错返回,不会阻塞等待,返回ETIMEDOUT。
struct timespec有两个参数,第一个参数表示秒,第二个参数表示纳秒。

4.解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

5.摧毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥锁应用

死锁

学习了互斥锁之后,我们来看看什么是死锁?
程序当中,各个执行流都占有一把锁,但是都还想申请对方的锁,但是对方的锁被对方占用着,所以所有执行流都因为获取不到对方的锁而陷入阻塞。这就叫做死锁

死锁的四个必要条件
1.互斥条件:就是一把锁只能被一个线程拥有。
2.请求与保持条件:保持(执行流已经有了一把锁),请求(还想拥有新的锁)。
3.循环等待:很多个执行流之间请求锁资源的情况形成了闭环。
4.不可剥夺:别的执行流不能帮本执行流释放锁资源。

死锁检测算法
https://blog.csdn.net/weixin_34249678/article/details/94155961
https://blog.csdn.net/sinat_22055459/article/details/51454468

那么如何避免死锁:
1.破坏死锁的必要条件。
2.加锁顺序一致。
3.避免未释锁资源。
4.一次性分配所有资源

读写锁

我们之前学习了互斥锁,是为了保证访问临界资源的原子性。其实读写锁与互斥锁类似。不过读写锁允许更高的并行性,读写锁有三个状态:读模式下加锁、写模式下加锁、不加锁。读写锁只能有一个执行流占用写模式的读写锁,但可以有多个线程同时访问读模式的读写锁。当一个执行流进行写时,其他线程不能读也不能写。当一个执行流进行读时,其他线程可以读,不能写。

那么为什么可以有多个线程同时去占有读模式的读写锁呢?
这是因为读写锁中有引用计数器,当多个线程拿着以读模式打开的读写锁,释放一次,计数器减1。加锁一次计数器加1。

此处需要明白两个问题
1.当多个执行流以读的状态获取读写锁的时候,这时,有一个执行流想以写的模式进行获取读写锁,底层会怎么样?
阻塞该执行流,直到所有进行读操作的执行流释放读写锁。
2.当多个执行流以读的状态获取读写锁的时候,这时,有一个执行流想以写的模式进行获取读写锁,随后又有一个执行流想以读的模式获取读写锁,那么会不会发生后面想要以读模式打开的线程继续获取读写锁,而导致以写模式的执行流一直阻塞?
不会的,通常不会让想写的线程等太长时间。操作系统会阻塞后面想读的执行流,保证想写的执行流不会等太久。避免锁资源被长时间占用。

读写锁相关接口:
定义
pthread_rwlock_t
初始化:
int pthread_rwlock_init(pthread_rwlock_t* rwlock,pthread_rwlockattr_t* attr)
摧毁
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock)
加锁
读:int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock)
写:int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock)
解锁:一个解锁可以同时满足以写模式和读模式打开的读写锁。
int pthread_rwlock_unlock(pthread_rwlock_t* rwlock)

自旋锁

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋锁”的作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。

参考于:https://blog.csdn.net/qq_26093511/article/details/78634288

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值