同步线程中的互斥量(mutex)
本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完以后进行释放互斥量上面的锁。同时互斥变量使用pthread_mutex_t数据类型来表示。mutex对对象的值,只有0和1两个值。这两个值代表了mutex的两种状态。0表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则会进入排队等待;值为1,表示空闲状态,当前对象空闲,用户进程/线程可以Lock临界资源,之后mutex值减1变为0。
静态方式:
一、创建与销毁
注意:创建方式两种,销毁方式只有一种。
有两种方式创建锁——动态和静态。
POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
动态方式:
动态方式是使用pthread_mutex_init()函数来初始化互斥锁。
API:
int pthread_mutex_init(pthread_mutex_t *restrictmutex, const pthread_mutexattr_t *restrict mutexattr);
mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。如果成功返回0,否则返回错误编号。
销毁:
pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex) ;
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
二、锁的类型属性——mutexattr
* PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
三、锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex);
对互斥量 进行加锁,需要嗲用pthread_mutex_lock,如果互斥量已经上锁了,调用线程将阻塞知道互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock。
如果补希望被堵塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁,如果调用pthread_mutex_trylock时候互斥两处于未锁住状态,那么pthread_mutex_trylock会将锁住互斥两,不会出现阻塞并且返回0,否则pthread)mutex_trylock就会失败,不能锁住互斥量,从而返回EBUSY。
四、使用实例——使用mutex互斥量来保护数据结构
#include "apue.h"
#include <pthread.h>
struct foo{
int f_count;
pthread_mutex_t f_lock;
};
struct foo* foo_alloc(void)
{
struct foo *fp;
if((fp=malloc(sizeof(struct foo)))!=NULL){
fp->f_count=1;
if(pthread_mutex_init(&fp->f_lock,NULL)!=0){
free(fp);
return(NULL);
}
}
return (fp);
}
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if(--fp->f_count==0){
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
} else {
pthread_mutex_unlock(&fp->f_lock);
}
}
该程序描述了用于保护某个数据结构的互斥量是的使用方法。当多个线程要访问动态分配的对象时,可以在对象中嵌入引用计数,确保所有使用该对象的线程完成数据访问之前,该对象内存空间不会被释放。
在对引用计数加1、减1以及检查引用计数是否为0这些操作之前,都要锁住互斥量。(在初始化的时候没必要加锁,因为在这个操作之前分配线程是唯一引用该对象的线程。)
在使用该对象之前,线程需要先对这个对象的引用计数加1,当对象使用完毕以后,需要对引用计数减1。当最后一个引用被释放时,对象所占用的内存空间就被释放。
五、避免死锁
如果线程试图对同一个互斥量加两次锁,那么它自身就会陷入死锁状态,使用互斥量时,还有其他更加不明显的方式也可以产生死锁。
在避免死锁过程之中,可以先释放占有的锁,然后过一段时间再试。这种情况可以使用pthread_mutex_trylock接口避免死锁。
如果已经占有某些锁而且pthread_mutex_trylock接口返回成功,那么就可以前进;但是,如果不能获取锁,可以先释放自己已经占有的锁,做好清理工作,然后过一段时间重新尝试。