Fork me on GitHub

线程同步之互斥量

线程同步

当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图,如果每个线程使用的变量都是其他线程不会读取和修改的,那么就不存在一致性问题。

同样,如果变量是只读的,多个线程同时读取该变量也不会有一致性,但是,当一个线程可以修改的变量,其他线程也可以读取和修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容是不会访问到无效值的

为了解决这个问题,线程不得不使用锁,同一时间只允许一个线程访问该变量(互斥访问)。

互斥量

可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能回去再次等待它重新变为可用。在这种情况下,每次只有一个线程可以向前执行。

互斥变量是用pthread_mutex_t数据类型表示的。在使用互斥变量之前,必须首先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量),也可以调用pthread_mutex_init函数进行初始化。如果动态分配互斥量(例如,通过调用malloc函数),在释放内存前需要调用pthread_mutex_destroy。

1
2
3
4
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destory(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误编号;

如果使用默认初始化互斥量,只需把attr设置为NULL;
对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将会阻塞到互斥量被解锁。对互斥量解锁,需要调用pthread_mutex_unlock。

1
2
3
4
5
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,错误返回错误编号;

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁(如果互斥量被加锁,函数返回EBUSY,线程不会被阻塞)。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则失败不能锁住互斥量,而返回EBUSY;使用pthread_mutex_trylock()接口可以避免死锁。

示例

使用互斥量来实现三个线程分别打印出ABC,循环五次。 主要思路如下

1
2
3
4
5
6
mutex1 = 1
mutex2 = 0
mutex3 = 0
thread1 p(mutex1) v(mutex2)
thread2 p(mutex2) v(mutex3)
thread3 p(mutex3) v(mutex1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include "head.h"
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
pthread_mutex_t mutex3;
void Init(){ //initialization
int err;
err = pthread_mutex_init(&mutex1,NULL);
if(err != 0){
printf("init mutex1 error\n");
}
err = pthread_mutex_init(&mutex2,NULL);
if(err != 0){
printf("init mutex2 error\n");
}
err = pthread_mutex_init(&mutex3,NULL);
if(err != 0){
printf("init mutex3 error\n");
}
err = pthread_mutex_lock(&mutex2);
if(err != 0){
printf("mutex2 lock error\n");
}
err = pthread_mutex_lock(&mutex3);
if(err != 0){
printf("mutex3 lock error\n");
}
}
void* thr_fn1(void *arg){
int i;
int err;
for(i=0;i<5;++i){
err = pthread_mutex_lock(&mutex1);
if(err != 0){
printf("mutex1 lock error\n");
}
printf("thread1: A\n");
err = pthread_mutex_unlock(&mutex2);
if(err != 0){
printf("mutex2 unlock error\n");
}
}
return (void*)0;
}
void* thr_fn2(void *arg){
int i;
int err;
for(i=0;i<5;++i){
err = pthread_mutex_lock(&mutex2);
if(err != 0){
printf("mutex2 lock error\n");
}
printf("thread2: B\n");
err = pthread_mutex_unlock(&mutex3);
if(err != 0){
printf("mutex3 unlock error\n");
}
}
return (void*)0;
}
void* thr_fn3(void *arg){
int i;
int err;
for(i=0;i<5;++i){
err = pthread_mutex_lock(&mutex3);
if(err != 0){
printf("mutex3 lock error\n");
}
printf("thread3: C\n");
err = pthread_mutex_unlock(&mutex1);
if(err != 0){
printf("mutex1 unlock error\n");
}
}
return (void*)0;
}
void Destory(){
int err;
err = pthread_mutex_unlock(&mutex2);
if(err != 0){
printf("mutex2 unlock error\n");
}
err = pthread_mutex_unlock(&mutex3);
if(err != 0){
printf("mutex3 unlock error\n");
}
err = pthread_mutex_destroy(&mutex1);
if(err != 0){
printf("mutex1 destory error\n");
}
err = pthread_mutex_destroy(&mutex2);
if(err != 0){
printf("mutex2 destory error\n");
}
err = pthread_mutex_destroy(&mutex3);
if(err != 0){
printf("mutex3 destory error\n");
}
}
int main(void){
int err;
pthread_t tid1,tid2,tid3;
Init();
err = pthread_create(&tid1,NULL,thr_fn1,NULL);
if(err != 0){
printf("can't create thread1\n");
}
err = pthread_create(&tid2,NULL,thr_fn2,NULL);
if(err != 0){
printf("can't create thread2\n");
}
err = pthread_create(&tid3,NULL,thr_fn3,NULL);
if(err != 0){
printf("can't create thread2\n");
}
err = pthread_join(tid1,NULL);
if(err != 0){
printf("can't join with thread1\n");
}
err = pthread_join(tid2,NULL);
if(err != 0){
printf("can't join with thread2\n");
}
err = pthread_join(tid3,NULL);
if(err != 0){
printf("can't join with thread3\n");
}
Destory();
exit(0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[vrlive@iZ23chs2r19Z eleven]$ gcc pthreadMutex.c -lpthread -o pthreadMutex.o
[vrlive@iZ23chs2r19Z eleven]$ ./pthreadMutex.o
thread1: A
thread2: B
thread3: C
thread1: A
thread2: B
thread3: C
thread1: A
thread2: B
thread3: C
thread1: A
thread2: B
thread3: C
thread1: A
thread2: B
thread3: C

注意:pthread_mutex_lock和pthread_mutex_unlock必须成对出现,不然pthread_mutex_destory函数调用出错。

避免死锁

1.线程A试图对用一个互斥量mutexA加锁两次,那么它自身就会陷入死锁状态,

用伪代码表示就是:

1
2
pthread_mutex_lock(&mutexA)
pthread_mutex_lock(&mutexA) /* 这里死锁 */

2.程序中使用多个互斥量时,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时就发生死锁。因为两个线程都在互相请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是产生死锁

用伪代码表示就是:

1
2
3
4
5
6
7
8
9
10
pthreadA:
pthread_mutex_lock(&mutexA)
pthread_mutex_lock(&mutexB) /* 这里死锁 */
pthreadB:
pthread_mutex_lock(&mutexB)
pthread_mutex_lock(&mutexA) /* 这里死锁 */

有时候,应用程序的结构使得对互斥量进行排序是很困难的。如果涉及了太多的锁和数据结构,可用的函数并不能把它转换成简单的层次,那么就需要采用另外的方法。在这种情况下,可以先释放占有的锁,然后过段时间再试。这种情况可以使用pthread_mutex_trylock接口避免死锁。如果已经占有某些锁而且pthread_mutex_trylock接口返回成功,那么就可以前进。但是,如果不能获取锁,可以先释放已经占有的锁,做好清理工作,然后过一段时间再重新试。

函数pthread_mutex_timedlock

当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock函数与pthread_mutex_lock是基本等价的,但是在达到超时时间值时,pthread_mutex_timelock不会对互斥量进行加锁,而是返回错误码ETIMEDOUT

1
2
3
4
5
6
#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict tsptr);
//返回值:若成功,返回0;否则,返回错误编码

超时指定愿意等待的绝对时间(某个具体的时间)(与相对时间对比而言,指定在时间X之前可以阻塞等待,而不是说愿意阻塞Y秒)。这个超时时间是用timespec结构来表示,它用秒和纳秒来描述时间。

示例

下面示例给出了如何用pthread_mutex_timedlock避免永久阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "head.h"
int main(void){
int err;
struct timespec tout;
struct tm *tmp;
char buf[64];
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
printf("mutex is locked\n");
clock_gettime(CLOCK_REALTIME,&tout);
tmp = localtime(&tout.tv_sec);
strftime(buf,sizeof(buf),"%r",tmp);
printf("current time is %s\n",buf);
tout.tv_sec += 10; //10 seconds from new
//caution: this could lead to deadlock
err = pthread_mutex_timedlock(&lock,&tout);
clock_gettime(CLOCK_REALTIME,&tout);
tmp = localtime(&tout.tv_sec);
strftime(buf,sizeof(buf),"%r",tmp);
printf("the time is now %s\n",buf);
if(err == 0){
printf("mutex locked again\n");
}else{
printf("can't lock mutex again:%s\n",strerror(err));
}
exit(0);
}
1
2
3
4
5
6
[vrlive@iZ23chs2r19Z eleven]$ gcc pthreadMutexTimelock.c -lpthread -o pthreadMutexTimelock.o
[vrlive@iZ23chs2r19Z eleven]$ ./pthreadMutexTimelock.o
mutex is locked
current time is 06:38:24 PM
the time is now 06:38:34 PM
can't lock mutex again:Connection timed out
-------------本文结束感谢您的阅读-------------

本文地址:http://www.wangxinri.cn/2018/05/19/线程同步之互斥量/
转载请注明出处,谢谢!

梦想夹带眼泪,咸咸的汗水!