目录
临界区
什么是互斥量
创建与销毁
1.创建互斥量
2.销毁互斥量
加锁与解锁
1.加锁
2.解锁
3.示例代码
死锁和避免
1.什么是死锁
2.死锁的避免
在计算机系统中有许多共享资源不允许用户并行使用。例如打印机,如果它同时进行两份文档打印,它的输出就会产生交错,从而都无法获得正确的文档。像打印机这样的共享设备被称为“排它性资源”,因为它一次只能由一个执行流访问。执行流必须以互斥的方式执行访问排它性资源的代码
临界区是必须以互斥方式执行的代码段,也就是说在临界区的范围内只能有一个活动的执行线程
互斥量,又称为互斥锁,是一种用来保护临界区的特殊变量,它可以处于锁定状态,也可以处于解锁状态:
(1)如果互斥锁是锁定的,就是一个特定的线程持有这个互斥锁
(2)如果没有线程持有这个互斥锁,那么这个互斥锁就处于解锁状态
每个互斥锁内部都有一个线程等待队列,用来保存等待该互斥锁的线程。当互斥锁处于解锁状态时,一个线程试图获取这个互斥锁时,这个线程就可以得到这个互斥锁而不会阻塞;当互斥锁处于锁定状态时,一个线程试图获取这个互斥锁时,这个线程将素色在互斥锁的等待线程队列内
互斥量是最简单也是最有效的县城同步机制。另外,互斥量只能被短时间的持有,使用完临界资源后应立即释放锁
pthreads使用pthread_mutex_t类型的变量来表示互斥量,同时在使用互斥量进行同步时需要先对它进行初始化,可以静态或动态方式对互斥量进行初始化
(1)静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
(2)动态初始化
int pthread_mutext_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
参数mutex是一个指向要初始化的互斥量的指针;参数attr传递NULL来初始化一个带有默认属性的互斥量,否则就要用类似于线程属性对象所使用的方法,先创建互斥量属性对象,再用该属性对象来创建互斥量
函数成功返回0,否则返回一个非0的错误码,下表为错误码:
错误码 | 出错描述 |
---|---|
EAGAIN | 系统缺乏初始化互斥量所需的非内存资源 |
ENOMEM | 系统缺乏初始化互斥量所需的内存资源 |
EPERM | 调用程序没有适当的优先级 |
以下代码用来动态的初始化默认属性的互斥量mylock:
int error;
pthread_mutex_t mylock;
if(error = pthread_mutex_init(&mylock,NULL))
fprintf(stderr,"Failed to initialize mylock: %s\n",strerror(error));
销毁互斥量使用pthread_mutex_destroy()函数,原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数mutex指向要销毁的互斥量的指针。以下代码销毁了mylock互斥量:
int error;
pthread_mutex_t mutex;
if(error = pthread_mutex_destroy(&mylock))
fprintf(stderr,"Failed to destroy mylock:%s\n",stderror(error));
Pthreads中有两个试图锁定互斥量的函数,pthread_mutex_lock()和pthread_mutex_trylock(),pthread_mutex_lock()函数会一直阻塞到互斥量可用为止,而pthread_mutex_trylock()会尝试加锁,通常立即返回,函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数mutex是需要加锁的互斥量。函数成功返回0,否则返回一个非0的错误码,其中另一个线程已持有锁的情况下,调用pthread_mutex_trylock()函数的额错误码是EBUSY
pthread_mutex_unlock()函数是用来释放指定的互斥量。函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数是需要解锁的互斥量。成功返回0;否则返回一个非0的错误码
只有当线程需要进入临界区之前正确地获取适当的互斥量,并在线程离开临界区时释放互斥量。以下伪代码展示了互斥量保护临界区的基本用法:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
#include
#include
#include
#include
pthread_t tid[2];
pthread_mutex_t lock;
void *doPrint(void *arg)
{
int id=(long)arg;
int i=0;
pthread_mutex_lock(&lock); /*使用互斥量保护临界区*/
printf("Job %d started\n",id);
for(int i=0;i<5;i++)
{
printf("Job %d printfing\n",id);
usleep(10);
}
printf("Job %d finished\n",id);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(int argc, char *argv[])
{
long i=0;
int err;
if(pthread_mutex_init(&lock,NULL)!=0)
{
printf("Mutex init failed\n");
return 1;
}
while(i<2)
{
err=pthread_create(&tid[i],NULL,&doPrint,(void *)i);
if(err!=0)
printf("Can't create thread:[%s]\n",strerror(err));
i++;
}
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_mutex_destroy(&lock);
return 0;
}
运行结果如下所示,Job1先获取互斥锁并进行打印,所有Job1需要打印的完成后才让Job0打印
死锁是指两个或两个以上的执行序在执行过程中,因争夺资源而造成的一种互相等待的现象。例如:一个线程T1已锁定一个资源R1,又想去锁定资源R2,而此时另一个线程T2已锁定了资源R2,却想去锁定R1,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。下程序清单示例了死锁发生的情况:
#include
#include
#include
#include
pthread_t tid[2];
pthread_mutex_t mutexA=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB=PTHREAD_MUTEX_INITIALIZER;
void *t1(void *arg)
{
pthread_mutex_lock(&mutexA);
printf("t1 get mutexA\n");
usleep(1000);
pthread_mutex_lock(&mutexB);
printf("t1 get mutexB\n");
pthread_mutex_unlock(&mutexB);
printf("t1 release mutexB\n");
pthread_mutex_unlock(&mutexA);
printf("t1 release mutexA\n");
return NULL;
}
void *t2(void *arg)
{
pthread_mutex_lock(&mutexB);
printf("t2 get mutexA\n");
usleep(1000);
pthread_mutex_lock(&mutexA);
printf("t2 get mutexB\n");
pthread_mutex_unlock(&mutexA);
printf("t2 release mutexB\n");
pthread_mutex_unlock(&mutexB);
printf("t2 release mutexA\n");
return NULL;
}
int main(int argc, char *argv[])
{
int err;
err=pthread_create(&(tid[0]),NULL,&t1,NULL);
if(err!=0)
printf("Can't create thread:[%s]\n",strerror(err));
err=pthread_create(&(tid[1]),NULL,&t2,NULL);
if(err!=0)
printf("Can't create thread:[%s]\n",strerror(err));
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
return 0;
}
下图为代码运行结果,两个线程互相等待,进入死锁
当多个线程需要相同的一些锁, 但是按照不同的顺序加锁, 死锁就很容易发生, 如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。 例如,规定程序内有三个互斥锁的加锁顺序为 mutexA->mutexB->mutexC,则线程 t1、 t2、 t3 线程操作伪代码如下所示:
t1 | t2 | t3 |
lock(mutexA) | lock(mutexA) | lock(mutexB) |
lock(mutexB) | lock(mutexC) | lock(mutexC) |
lock(mutexC) |