本篇我记录一下自己学习互斥锁的成果。
线程同步的概述:
当线程A在对一个共享资源执行写操作时,如果此时线程B恰好也对这个共享资源执行写操作,那么在进程中就会出现两个线程同时对这个共享资源进行写操作,会出现什么结果呢?当然是会破坏我们写入的数据,造成不可预计的错误。
而使用互斥锁(mutex)就正好可以解决这一问题,当线程A在操作(读/写)共享资源时,对其进行加锁,此时如果线程B也要对这个共享资源进行操作时,由于这个锁的存在会线程B被阻塞,直到线程A释放掉它之前加在这个共享资源上的锁时,线程B才会变成可运行状态。
我使用伪代码来表示:
lock(&mutex);
访问共享资源
unlock(&mutex);
其本质并不是把数据“锁住”,而是为线程提供一种串行化执行的机制(让不同的线程避免在同一时间去操作数据),我曾做过一个验证,fun1和fun2是供不同线程执行的程序,如果我注销掉fun2的锁,并且在fun1锁住后不释放,那么在线程调用fun2的时候它依旧会操作到数据。
正常情况下,如果fun1和fun2遵循我们的互斥锁约定,当其中任何一个线程获得互斥锁后,其他试图获得这个锁的线程都会被阻塞,这样就巧妙的将两个线程分别安排在不同的时段执行,保证了任何时刻下只有唯一的线程有“权利”去操作这个共享资源。(我一开始也是误解了互斥锁的真正含义,后来经过多次实例验证才知道这点)
代码如下:
…
int num = 3;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_t TID1, TID2;
/*线程调用函数*/
void *fun1(void *arg)
{
int ret;
ret =pthread_mutex_lock(&lock);
if (0 != ret)
{
printf("pthreada lock error\n");
return ((void *)-1);
}
num = num + 1;
printf("in thepthread A num is %d\n", num);
while(1);
//pthread_mutex_unlock(&lock);
return ((void *)-1);
}
/*线程调用函数*/
void *fun2(void *arg)
{
int ret;
//ret =pthread_mutex_lock(&lock);
if (0 != ret)
{
printf("pthreadb lock error\n");
return ((void *)-1);
}
int num = 77;
printf("in thepthread B num is %d\n", num);
//pthread_mutex_unlock(&lock);
return ((void *)-1);
}
互斥锁的初始化:
互斥锁是使用 pthread_mutex_t数据类型来表示的。
在使用互斥锁之前要对其进行初始化。
- 静态分配
Eg.
Pthread_mutex_tlock = PTHREAD_MUTEX_INITIALIZER
- 动态分配
Eg.
Structfoo
{
intf_count;
pthread_mutex_tf_lock’
intfid;
…
}
Structfoo *fp;
Fp= malloc(sizeof(struct foo));
Pthread_mutex_init(&fp->f_lock,&NULL);
…
Pthread_mutex_destory(&fp->f_lock);
Free(fp);
上面可以看到,
在动态初始化的时候,用到了如下两个函数:
- 互斥锁初始化:
在动态初始化之前要申请好内存
intpthread_mutex_init(pthread_mutex_t *restrict mutex,
constpthread_mutexattr_t *restrict attr);
- 销毁互斥锁
在销毁之前要释放动态申请的内存
intpthread_mutex_destory(pthread_mutex_t *mutex);
互斥锁的加锁&解锁:
- 对互斥锁进行加锁
int pthread_mutex_lock(pthread_mutex_t*mutex);
返回值:若成功,返回0;否则返回错误编号
线程A调用该函数加锁以后,如果线程B再调用这个函数对线程A加过锁的互斥锁进行加锁,线程B将被阻塞直到线程A释放这个锁。
- 尝试对互斥锁进行加锁
intpthread_mutex_trylock(pthread_mutex_t *mutex);
返回值:若成功,返回0;否则返回错误编号
如果调用该函数时,互斥锁处于未解锁状态,那么pthread_mutex_trylock将会锁住互斥锁,不会出现阻塞,直接返回0,否则就会失败,并返回EBUSY
- 对互斥锁进行解锁
intpthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:若成功,返回0;否则返回错误编号
避免死锁:
- 如果线程试图对同一个互斥锁加锁两次,那么他就会陷入死锁状态;
- 线程A一直占着 互斥锁1 ,这时它试图对互斥锁2 进行加锁,
线程B此时占着 互斥锁2,此时它也试图对 互斥锁1 进行加锁;
这种情况下就出现了死锁的场景。
避免这种死锁的方式:
1. 线程B可以先释放自己目前占有的锁,过一段时间再去尝试加锁;
2. 让两个线程以相同的顺序加锁,这样可以避免死锁
互斥锁的属性:
互斥锁属性是用pthread_mutexattr_t结构表示的,对于非默认属性可以使用如下俩个函数来初始化和反初始化:
intpthread_mutexattr_init(pthread_mutexattr_t *attr);
lint pthread_mutexattr_destroy(pthread_mutexattr_t*attr);
两个函数的返回值:若成功,返回0;若失败,返回错误编号
共享属性:
该属性有两种情况:
- PTHREAD_PROCESS_PRIVATE 这种是默认情况,表示互斥锁只能在本进程中使用
- PTHREAD_PROCESS_SHARED 表示互斥锁可以在不同进程间使用
使用如下两个函数来设置:
1. int pthread_mutexattr_getpshared(constpthread_mutexattr_t *restrict attr,int *restrict pshared);
2. intpthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);
返回值:若成功,返回0;若失败,返回错误编码