线程的同步与互斥
一、mutex(互斥量)
·大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
·但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完后线程之间的交互
·多个线程并发的操作共享变量,会带来问题
例:操作共享变量会有问题的售票系统
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<pthread.h>
6
7 int ticket = 100;
8 void* route(void *arg)
9 {
10 char* id = (char*)arg;
11 while(1){
12 if(ticket > 0){
13 sleep(1);
14 printf("%s sells ticket:%d\n",id,ticket);
15 ticket--;
16 }
17 else{
18 break;
19 }
20 }
21 }
22 int main()
23 {
24 pthread_t t1,t2,t3,t4;
25
26 pthread_create(&t1,NULL,route,"thread1");
27 pthread_create(&t2,NULL,route,"thread2");
28 pthread_create(&t3,NULL,route,"thread3");
29 pthread_create(&t4,NULL,route,"thread4");
30
31 pthread_join(t1,NULL);
32 pthread_join(t2,NULL);
33 pthread_join(t3,NULL);
34 pthread_join(t4,NULL);
35 }
为什么会对卖3张呢?
·if语句判断条件为真以后,代码可以并发的切换到其他线程
·sleep这个模拟漫长业务过程,在这个漫长的过程中,可能有其他很多的线程进入该代码段
·--ticket操作本身就不是一个原子操作
--操作并不是原子操作,而是对应三条汇编指令
·load:将共享变量ticket从内存加载到寄存器中
·update:更新寄存器里面的值,执行-1操作
·store:将新值从寄存器写回共享变量ticket的内存地址
解决上面问题就必须做到:
·代码必须要有互斥行为,当代码进入临界区执行时,不允许其他线程进入该临界区
·如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
·如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区
要做到这三点就必须要有一把锁。Linux上提供的这把锁叫互斥量
二、互斥量的接口
1、初始化
方法一:静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
方法二:动态分配
参数:mutex——要初始化的互斥量
attr——NULL
2、销毁互斥量
注意:
·使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁、
·不要销毁一个已经加锁的互斥量
·已经销毁的互斥量,要确保后面不会有线程尝试加锁
3、互斥量加锁和解锁
返回值:成功返回0,失败返回错误码
调用pthread_mutex_lock会遇到的情况:
·互斥量处于未锁状态,该函数会将互斥量锁定,同时成功返回
·发起函数调用时,其他线程已经锁住互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock调用会陷入阻塞,等待互斥量解锁
改进上面的售票代码
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<pthread.h>
6 #include<sched.h>
7
8 int ticket = 100;
9 pthread_mutex_t mutex;
10
11 void* route(void *arg)
12 {
13 char* id = (char*)arg;
14 while(1){
15 pthread_mutex_lock(&mutex);
16 if(ticket > 0){
17 sleep(1);
18 printf("%s sells ticket:%d\n",id,ticket);
19 ticket--;
20 pthread_mutex_unlock(&mutex);
21 }
22 else{
23 pthread_mutex_unlock(&mutex);
24 break;
25 }
26 }
27 }
28 int main()
29 {
30 pthread_t t1,t2,t3,t4;
31
32 pthread_mutex_init(&mutex,NULL);
33
34 pthread_create(&t1,NULL,route,"thread1");
35 pthread_create(&t2,NULL,route,"thread2");
36 pthread_create(&t3,NULL,route,"thread3");
37 pthread_create(&t4,NULL,route,"thread4");
38
39 pthread_join(t1,NULL);
40 pthread_join(t2,NULL);
41 pthread_join(t3,NULL);
42 pthread_join(t4,NULL);
43
44 pthread_mutex_destroy(&mutex);
45 }
三、条件变量
·当一个线程互斥的访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
·例如一个线程访问队列时,发现队列为空,它只能等,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量
条件变量函数
1、初始化
参数:cond——要初始化的条件变量
attr——NULL
2、销毁
3、等待条件满足
参数:cond——要在这个条件变量上等待
mutex——互斥量
4、唤醒等待
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<pthread.h>
6
7 pthread_cond_t cond;
8 pthread_mutex_t mutex;
9
10 void *r1(void *arg)
11 {
12 while(1){
13 pthread_cond_wait(&cond,&mutex);
14 printf("active\n");
15 }
16 }
17 void *r2(void *arg)
18 {
19 pthread_cond_signal(&cond);
20 sleep(1);
21 }
22 int main()
23 {
24 pthread_t t1, t2;
25
26 pthread_cond_init(&cond,NULL);
27 pthread_mutex_init(&mutex,NULL);
28
29 pthread_create(&t1,NULL,r1,NULL);
30 pthread_create(&t2,NULL,r2,NULL);
31
32 pthread_join(t1,NULL);
33 pthread_join(t2,NULL);
34
35 pthread_mutex_destroy(&mutex);
36 pthread_cond_destroy(&cond);
37 }
pthread_cond_wait需要互斥量的原因:
(1)条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
(2)条件变量不会无缘无故的突然就满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据
按照上面的说法,我们设计出:先上锁,发现条件不满足,解锁,然后等待条件变量上
//错误的设计
pthread_mutex_lock(&mutex);
while(condition){
pthread_mutex_unlock(&mutex);
//解锁后,等待之前,条件可能已经满足,信号发出,但是该信号可能被错过
pthread_cond_wait(&cond);
}
pthread_mutex_unlock(&mutex);
·由于解锁和等待不是原子操作。调用解锁之后pthread_mutex_unlock之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么pthread_cond_wait将错过这个信号,可能会导致线程永远阻塞在这个pthread_cond_wait。所以解锁和等待必须是原子操作
·int pthread_cond_wait(pthrad_cond_t *cond,pthread_mutex_t *mutex);进入该函数后,会去看条件变量等于0不?等于,把互斥量变为1,直到cond_wait返回,把条件变量改为1,把互斥量恢复成原来的样子
五、条件变量的使用范围
1、等待条件代码
pthread_mutex_lock(&mutex)
while(条件为假)
pthread_cond_waiit(cond,mutex);
修改条件
pthread_mutex_unlock(&mutex);
2、给条件发送信号代码
pthread_mutex_lock(&mutex);
设置条件
pthread_cond_signal(cond);
pthread_mutex_unclock(&mutex);
上一篇: 迪杰特斯拉算法堆优化
下一篇: 线程的互斥与同步