互斥锁与死锁

在多线程程序中,多个线程可能会共用同一个对象,为了防止多个线程在争夺、使用同一份对象时可能会对该对象造成的改变,引入互斥锁。互斥锁可保证在任一时刻,只能有一个线程访问该对象,从而保证共享数据操作的完整性。

互斥锁基本原理:

互斥锁是一个二元变量,其状态为开锁(允许0)和上锁(禁止1),将某个共享资源与某个特定互斥锁在逻辑上绑定(要申请该资源必须先获取锁)。
(1)访问公共资源前,必须申请该互斥锁,若处于开锁状态,则申请到锁对象,并立即占有该锁,以防止其他线程访问该资源;如果该互斥锁处于锁定状态,则阻塞当前线程或返回busy。
(2)只有锁定该互斥锁的进程才能释放该互斥锁,其他线程试图释放无效。
(3)互斥锁在同一个线程内,没有互斥的特性。

从互斥锁的行为看,线程加锁和解锁之间的代码相当于一个独木桥,同一时刻只有一个线程能执行。从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了。比较专业的叫法是同步执行,这段代码区域叫临界区。同步执行就破坏了线程并行性的初衷了,临界区越大破坏得越厉害。所以在实际应用中,应该尽量避免有临界区出现。实在不行,临界区也要尽量的小。

互斥锁主要函数

初始化
静态方式初始化

POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。

动态方式初始化
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr); 

参数:
mutex:表示初始化的互斥锁的指针。
attr:指向互斥锁对象属性的指针,通常情况下初始化为NULL,即使用默认属性。

申请互斥锁

如果一个线程要占用共享资源,必须先申请对应互斥锁,使用函数:

int pthread_mutex_lock(pthread_mutex_t *mutex);  
int pthread_mutex_trylock(pthread_mutex_t *mutex);  

这两种方式的区别是,前者采用阻塞方式申请互斥锁,当一个线程在申请共享资源时发现该资源已经被锁定,就会阻塞等待。而pthread_mutex_trylock却不会阻塞,当要申请共享资源时发现该资源已经被锁定时,会返回一个EBUSY信号,使用pthread_mutex_trylock的目的是提高程序运行的并行性。

释放互斥锁

也称为解锁,使用函数:

int pthread_mutex_unlock(pthread_mutex_t *mutex);  

释放只能由占有该互斥锁的线程完成,如果释放成功,返回0,失败返回错误编号。

销毁互斥锁

使用函数:

int pthread_mutex_destory(pthread_mutex_t *mutex );  

销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

死锁

什么是死锁

一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。
另一种:若线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都永远处于挂起状态了。

死锁产生的四个必要条件

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

循环等待条件:若干进程间形成首尾相接循环等待资源的关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

如何避免死锁
预防死锁:

我们可以通过破坏死锁产生的4个必要条件来预防死锁。
首先明确一下资源互斥是资源使用的固有特性是无法改变的,所以只能从其他3个条件入手。

破坏”请求与保持条件“:第一种方法,静态分配,即每个进程在开始执行时就申请他所需要的全部资源。第二种方法,动态分配,即每个进程在申请所需要的资源时他本身不占用系统资源。

破坏“不可剥夺”条件:一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源,以及新申请的资源才可以重新启动,执行。

破坏“循环等待”条件:采用资源有序分配法。其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

避免死锁:

死锁避免的基本思想:系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。

解除死锁

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

参考:
1.https://blog.csdn.net/jiajun2001/article/details/12624923
2.https://blog.csdn.net/jyy305/article/details/70077042

猜你喜欢

转载自blog.csdn.net/hmxz2nn/article/details/80039347