互斥锁(互斥量)是线程用来同步彼此行为的工具。互斥锁可以帮助线程同步对共享资源的使用,以防如下情况发生:线程某甲试图访问一共享变量时,线程某乙正在对其修改。
未避免线程更新共享变量时所出现的问题,必须使用互斥锁来确保同时仅有一个线程可以访问某项共享资源。
互斥锁有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥锁。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法。一旦线程锁定互斥锁,随机成为该互斥锁的所有者,只有所有者才能给互斥锁解锁。
互斥锁初始化:
互斥锁是属于pthread_mutex_t类型的变量,使用之前必须对其初始化。
初始化有两种方式:一种是静态赋值法,另一种是动态初始化。
静态赋值法只适用于静态分配的互斥量,如静态变量和全局变量定义的方式,且只能赋值普通锁
赋值形式:pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER
PTHREAD_MUTEX_INITIALIZER是指将互斥锁初始化为普通锁。锁的不同种类在下面会讲到。
动态初始化互斥锁是通过pthread_mutex_init函数初始化互斥锁,该函数原型如下:
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);
动态初始化互斥锁适用于以下情况:
1.动态分配于堆中的互斥锁。
2.互斥锁是在栈中分配的自动变量。
3.初始化经由静态分配,但不使用默认属性的普通锁
以上情况只能用动态初始化函数初始化互斥锁。
函数中的参数mutexattr表示互斥锁的属性,如果为NULL则使用默认属性(普通锁)。互斥锁的属性及意义如下:
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
* PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
* PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
* PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
初始化以后,就可以对互斥锁进行加锁和解锁操作了。
锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
用pthread_mutex_lock()加锁时,如果mutex已经被锁住,当前尝试加锁的线程就会阻塞,直到互斥锁被其他线程释放。
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex)
解锁时要满足两个条件:
一是互斥锁必须处于加锁状态,二是调用本函数的线程必须是给互斥锁加锁的进程。
摧毁互斥锁:
当不再需要经由自动或动态分配的互斥锁时,应使用pthread_mutex_destory()将其摧毁。(对于使用PTHREAD_MUTEX_INITIALIZER静态初始化的互斥锁,无需调用pthread_mutex_destory())。
#include<pthread.h>
int pthread_mutex_destory(pthreead_mutex_t* mutex);
以下是一个利用互斥锁的一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_NUM 3
#define REPEAT_NUM 3
#define DELAY_TIME_LEVLES 5.0
pthread_mutex_t mutex;
void *thread_fun(void *arg)
{
int no = (int) arg;
int delay;
int ret,i;
ret = pthread_mutex_lock(&mutex); //返回0为成功
if(ret)
{
printf("Thread %d lock failed\n",no);
pthread_exit(NULL);
}
for(i=0;i<REPEAT_NUM;i++)
{
delay = (int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))+1;
sleep(delay);
printf("Thread %d: job %d delay = %d\n",no,i,delay);
}
printf("Thread %d finished\n",no);
pthread_exit
}
int main()
{
pthread_t thread[THREAD_NUM];
int i,ret;
void *thrd;
srand(time(NULL));
pthread_mutex_init(&mutex,NULL);
for(i=0;i<THREAD_NUM;i++)
{
ret = pthread_create(&thread[i],NULL,thread_fun,(void *)i);
if(ret!=0)
{
printf("Create %d failed\n",i);
exit(ret);
}
}
printf("Create thread success!\n Waiting for threads to finish ...\n");
for(i=0;i<THREAD_NUM;i++)
{
ret = pthread_join(thread[i],&thrd);
pthread_mutex_unlock(&mutex);
}
pthread_mutex_destroy(&mutex);
return 0;
}
死锁现象:
死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
接下来介绍两个典型死锁现象例子,写程序时应避免此类现象。
1.线程发起pthread_mutex_lock()后,未解锁再次调用pthread_mutex_lock()进行上锁。此时线程会陷入无限等待状态。
2.线程A 线程B
1.pthread_mutex_lock(&mutex1); 1.pthread_mutex_lock(&mutex2);
2.pthread_mutex_lock(&mutex2); 2.pthread_mutex_lock(&mutex1);
这两个线程中的每个线程都成功地锁住一个互斥量,接着试图对己为另一个线程锁定的互斥量加锁。两个线程将无限期地等待下去。
要避免此类死锁问题,最简单的方法是定义互斥量的层次关系。当多个线程对一组互斥量操作时,总是应该以相同的顺序对该组互斥量进行锁定。