1.互斥锁作用:
互斥锁mutex是用来保护线程间共享的全局变量安全的一种机制,保证多线程中在某一时刻只允许某一个线程对临界区的访问。
2.初始化方式:
互斥锁对象的数据类型是 pthread_mutex_t ;
互斥锁的初始方式分为静态方式和动态方式:
//动态初始化互斥锁方式
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//静态初始化互斥锁方式
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3.常用函数:
//阻塞方式加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//非阻塞方式加锁,如果该互斥体已经被上锁,该调用不会阻塞等待,而会返回一个错误代码,没被加锁则加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//阻塞等待线程锁,超过固定的时间还不能获取锁,则返回错误
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
4.互斥锁的属性
互斥量属性对象的数据类型为:pthread_mutexattr_t
#include <pthread.h>
//初始化一个互斥对象的属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
//回收属性对象
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
一般有两个属性值得关心:进程共享属性和类型属性。但是Linux下只支持类型属性。
进程共享属性:用来获取和设置互斥量是否被共享
int pthread_mutexattr_getpshared(const pthread_mutexattr_t * restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
如果属性设置为PTHREAD_PROCESS_SHARED,那么从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。
如果属性设置为PTHREAD_PROCESS_PRIVATE,那么互斥量只能用于一个进程中的多个线程同步。这个是默认的属性。
类型属性:
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind);
不支持POSIX标准的kind值以NP结尾:不能在可移植的程序中使用
- PTHREAD_MUTEX_FAST_NP //默认属性,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP //嵌套锁,允许同一个线程对同一个锁成功获得多次(没有解锁后重新加锁不会死锁),并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP //检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_FAST_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
一个线程调用pthread_mutex_lock获取已经被占用的互斥量的时候,当类型属性为”fast”,该线程会被暂停。当类型属性为”error”,该线程立刻返回一个错误码EDEADLK。当属性为”recursive”,该线程会成功返回(但是mutex对象内部会有一个值记录目前有多少线程占有该互斥量,当所有占有的线程都调用pthread_mutex_unlock,该互斥量才处于未被占有的状态)
支持POSIX标准的kind值:
- PTHREAD_MUTEX_NORNAL //不做任何特殊的错误检查或死锁检测,没有解锁后重新加锁会死锁。
- PTHREAD_MUTEX_RECURSIVE //嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争;
- PTHREAD_MUTEX_ERRORCHECK //检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,即没有解锁后重新加锁会返回错误;
- PTHREAD_MUTEX_ADAPTIVE //适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
5.递归锁与非递归锁(嵌套锁与非嵌套锁)
mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。
二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。
Windows下的Mutex和Critical Section是可递归的。Linux下的pthread_mutex_t锁默认是非递归的。可以显示的设置PTHREAD_MUTEX_RECURSIVE属性,将pthread_mutex_t设为递归锁。
MutexLock mutex;
void foo()
{
mutex.lock();
// do something
mutex.unlock();
}
void bar()
{
mutex.lock();
// do something
foo();
mutex.unlock();
}
foo函数和bar函数都获取了同一个锁,而bar函数又会调用foo函数。如果MutexLock锁是个非递归锁,则这个程序会立即死锁。因此在为一段程序加锁时要格外小心,否则很容易因为这种调用关系而造成死锁。
6.例子
example_1.cpp
8
9 #include <pthread.h>
10 #include <iostream>
11
12
13 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
14
15 int count = 0;
16 void* threadFunc(void*)
17 {
18 for(int i = 0; i <= 10; ++i)
19 {
20 pthread_mutex_lock(&mutex);
21 count += 1;
22 std::cout<<pthread_self() <<":"<< count << std::endl;
23 pthread_mutex_unlock(&mutex);
24 }
25 }
26
27
28 int main()
29 {
30 pthread_t tidA, tidB;
31
32 pthread_create(&tidA, NULL, &threadFunc, NULL);
33 pthread_create(&tidB, NULL, &threadFunc, NULL);
34
35 pthread_join(tidA, NULL);
36 pthread_join(tidB, NULL);
37 return 0;
38 }
【转载】死锁原因及解决、避免办法
死锁的条件
- 互斥条件(Mutual exclusion) :资源不能被共享,只能由一个进程使用。
- 请求与保持条件(Hold and wait):进程已获得了一些资源,但因请求其它资源被阻塞时,对已获得的资源保持不放。
- 不可抢占条件(No pre-emption) :有些系统资源是不可抢占的,当某个进程已获得这种资源后,系统不能强行收回,只能由进程使用完时自己释放。
- 循环等待条件(Circular wait) :若干个进程形成环形链,每个都占用对方申请的下一个资源。
处理死锁的策略
1、忽略该问题。例如鸵鸟算法。
2、检测死锁并且恢复。
3、仔细地对资源进行动态分配,以避免死锁。
4、通过破除死锁四个必要条件之一,来防止死锁产生。
鸵鸟算法:
该算法可以应用在极少发生死锁的的情况下。为什么叫鸵鸟算法呢,因为传说中鸵鸟看到危险就把头埋在地底下,可能鸵鸟觉得看不到危险也就没危险了吧。跟掩耳盗铃有点像。
银行家算法:
所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。
按照银行家算法的思想,当进程请求资源时,系统将按如下原则分配系统资源:
(1) 当一个进程对资源的最大需求量不超过系统中的资源数时可以接纳该进程。
(2) 进程可以分期请求资源,当请求的总数不能超过最大需求量。
(3) 当系统现有的资源不能满足进程尚需资源数时,对进程的请求可以推迟分配,但总能使进程在有限的时间里得到资源。
(4) 当系统现有的资源能满足进程尚需资源数时,必须测试系统现存的资源能否满足该进程尚需的最大资源数,若能满足则按当前的申请量分配资源,否则也要推迟分配。
解决死锁的策略
对待死锁的策略主要有:
(1) 死锁预防:破坏导致死锁必要条件中的任意一个就可以预防死锁。例如,要求用户申请资源时一次性申请所需要的全部资源,这就破坏了保持和等待条件;将资源分层,得到上一层资源后,才能够申请下一层资源,它破坏了环路等待条件。预防通常会降低系统的效率。
(2) 死锁避免:避免是指进程在每次申请资源时判断这些操作是否安全,例如,使用银行家算法。死锁避免算法的执行会增加系统的开销。
(3) 死锁检测:死锁预防和避免都是事前措施,而死锁的检测则是判断系统是否处于死锁状态,如果是,则执行死锁解除策略。
(4) 死锁解除:这是与死锁检测结合使用的,它使用的方式就是剥夺。即将某进程所拥有的资源强行收回,分配给其他的进程。
死锁的避免:
死锁的预防是通过破坏产生条件来阻止死锁的产生,但这种方法破坏了系统的并行性和并发性。
死锁产生的前三个条件是死锁产生的必要条件,也就是说要产生死锁必须具备的条件,而不是存在这3个条件就一定产生死锁,那么只要在逻辑上回避了第四个条件就可以避免死锁。
避免死锁采用的是允许前三个条件存在,但通过合理的资源分配算法来确保永远不会形成环形等待的封闭进程链,从而避免死锁。该方法支持多个进程的并行执行,为了避免死锁,系统动态的确定是否分配一个资源给请求的进程。方法如下:
1.如果一个进程的当前请求的资源会导致死锁,系统拒绝启动该进程;
2.如果一个资源的分配会导致下一步的死锁,系统就拒绝本次的分配;
显然要避免死锁,必须事先知道系统拥有的资源数量及其属性