目录
线程安全
概念:
举例:
线程不安全的本质就是对临界区的非原子性访问导致的。
代码:
互斥:
什么是互斥:
互斥锁:
原理:
互斥锁的计数器如何保证原子性?
具体过程:
互斥锁的接口:
初始化:
动态初始化:
静态初始化:
加锁
验证:
假设这两个线程是线程A和线程B,假设线程A先拿到互斥锁,并没有释放互斥锁,而加锁的接口具有阻塞属性,因此应该会有如下现象:
程序不会退出,处于阻塞状态;整个工作都是由一个线程完成的,另一个线程并没有参与其中。
解锁:
修改上面的代码:
注意:在线程都有可能退出的地方都进行解锁,否则就有可能导致死锁。
销毁:
完整代码:
2 #include<pthread.h>
3 #include<stdio.h>
4 #include<unistd.h>
5
6 int g_t = 10;
7 pthread_mutex_t g_lock; //互斥锁
8
W> 9 void* thread_start(void* arg){
10 //修改全局变量
11 while(1){
12 sleep(1); //让程序结果更加明确
13 //加锁
14 pthread_mutex_lock(&g_lock);
15 if(g_t <= 0){
16 pthread_mutex_unlock(&g_lock);
17 break;
18 }
W> 19 printf("I am %p, i got value is %d\n", pthread_self(), g_t);
20 g_t--;
21 pthread_mutex_unlock(&g_lock);
22 }
W> 23 }
24 int main(){
25 //初始化互斥锁
26 pthread_mutex_init(&g_lock,NULL);
27 //创建两个线程
28 pthread_t tid[2];
29 for(int i = 0; i < 2; i++){
30 int ret = pthread_create(&tid[i], NULL, thread_start, NULL);
31 if(ret < 0){
32 perror("pthread_create");
33 return 0;
34 }
35 }
36 //主线程进行线程等待
37 for(int i = 0; i < 2; ++i){
38 pthread_join(tid[i], NULL);
}
40 //销毁互斥锁
41 pthread_mutex_destroy(&g_lock);
42 return 0;
43 }
同步
样例引入
现在有如下场景:
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<unistd.h>
4
5 #define THREAD_COUNT 1
6
7 int g_bowl = 0; //0没有面,1有面
8 pthread_mutex_t g_lock;
9
W> 10 void* eat_thread(void* arg){
11 while(1){
12 pthread_mutex_lock(&g_lock);
13 printf("I am eat thread, eat %d\n", g_bowl--);
14 pthread_mutex_unlock(&g_lock);
15 usleep(1);
16 }
17 }
W> 18 void* make_thread(void* arg){
19 while(1){
20 pthread_mutex_lock(&g_lock);
21 printf("I am make thread, make %d\n", g_bowl++);
22 pthread_mutex_unlock(&g_lock);
23 usleep(1);
24 }
25 }
26 int main(){
27 //初始化互斥锁
28 pthread_mutex_init(&g_lock, NULL);
29 pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
30 //创建吃面和做面线程
31 for(int i = 0; i < THREAD_COUNT; ++i){
32 int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
33 if(ret < 0){
34 perror("pthread_creat");
35 return 0;
36 }
37 ret = pthread_create(&make[i], NULL, eat_thread, NULL);
38 if(ret < 0){
39 perror("pthread_creat");
40 return 0;
41 }
42 }
43 //主线程等待工作线程
44 for(int i = 0; i < THREAD_COUNT; ++i){
45 pthread_join(eat[i], NULL);
46 pthread_join(make[i], NULL);
47 }
48 //销毁互斥锁
49 pthread_mutex_destroy(&g_lock);
50 return 0;
51 }
可以看到现在的程序访问碗这个临界资源是不合理的,因为我们只有一个碗,应该做一碗吃一碗。
结论:
应该如何实现同步呢?我们可以通过判断的方式解决。
结果貌似符合要求,但是存在问题,将代码改一下:
要解决这个问题,我们就需要了解条件变量,使用条件变量接口来解决。
条件变量:
条件变量原理:
条件变量的接口:
1、初始化接口
动态初始化:
静态初始化:
2、等待接口(哪个线程调用就将哪个线程放到条件变量对应的PCB等待队列中)
3、唤醒接口
4、销毁接口
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<unistd.h>
4
5 #define THREAD_COUNT 1
6
7 int g_bowl = 0; //0没有面,1有面
8 pthread_mutex_t g_lock; //互斥锁
9 pthread_cond_t g_cond; //条件变量
10
W> 11 void* eat_thread(void* arg){
12 while(1){
13 pthread_mutex_lock(&g_lock);
14 if(g_bowl == 0){
15 printf("我是吃面人,碗里面没面我就不吃了\n");
16 pthread_cond_wait(&g_cond, &g_lock);
17 //pthread_mutex_unlock(&g_lock);
18 //continue;
19 }
20 printf("I am eat thread, eat %d\n", g_bowl--);
21 //解锁
22 pthread_mutex_unlock(&g_lock);
23 //通知做面线程
24 pthread_cond_signal(&g_cond);
25 // usleep(1);
26 }
27 }
W> 28 void* make_thread(void* arg){
29 while(1){
30 pthread_mutex_lock(&g_lock);
31 if(g_bowl == 1){
32 printf("我是做面人,碗里面有面,我就不做了。。。\n");
33 pthread_cond_wait(&g_cond, &g_lock);
34 //pthread_mutex_unlock(&g_lock);
35 //continue;
36 }
37 printf("I am make thread, make %d\n", g_bowl++);
38 //解锁
39 pthread_mutex_unlock(&g_lock);
40 //通知吃面线程
41 pthread_cond_signal(&g_cond);
42 //usleep(1);
43 }
44 }
45 int main(){
46 //初始化互斥锁
47 pthread_mutex_init(&g_lock, NULL);
48 //初始化条件变量
49 pthread_cond_init(&g_cond, NULL);
50
51 pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
52 //创建吃面和做面线程
53 for(int i = 0; i < THREAD_COUNT; ++i){
54 int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
55 if(ret < 0){
56 perror("pthread_creat");
57 return 0;
58 }
59 ret = pthread_create(&make[i], NULL, eat_thread, NULL);
60 if(ret < 0){
61 perror("pthread_creat");
62 return 0;
63 }
64 }
65 //主线程等待工作线程
66 for(int i = 0; i < THREAD_COUNT; ++i){
67 pthread_join(eat[i], NULL);
68 pthread_join(make[i], NULL);
69 }
70 //销毁互斥锁
71 pthread_mutex_destroy(&g_lock);
72 //销毁条件变量
73 pthread_cond_destroy(&g_cond);
74 return 0;
75 }
对于多个吃面,结果又是如何呢?
可以看到当多个吃面线程和做面线程的时候,又出现了临界区资源访问不合理的现象。要想弄清楚这里的原由,我们需要先搞清楚pthread_cond_wait这个接口都做了些什么。
条件变量的夺命追问:
1、条件变量的等待接口第二个参数为什么会有互斥锁?
为什么要解锁呢?以一个吃面线程和一个做面线程为例:
2、pthread_cond_wait的内部针对互斥锁做了什么操作?先释放互斥锁还是先将线程放到PCB等待队列?
3、线程被唤醒之后会执行什么代码?
条件变量代码:
现在来分析为什么吃面线程和做面线程从1变为2后,程序就出错了:
假设有两个吃面线程A和B,两个做面线程C和D
下面的场景是有可能存在的:
解决方法:只需要将线程入口函数处的条件判断改为while,在等待接口退出的时候就会循环上去判断临界资源是否可用,进而保证临界资源访问的合理性:
用pstack查看以下线程的状态:
可以看到工作线程都处在了PCB等待队列中。
原因是:在线程通知PCB等待队列中的线程的时候,将同种类的线程通知出来了,然后判断临界资源是不可用状态,因此刚被通知出来的线程什么也没做,就又进入PCB等待队列中了。
如何解决:
只需要将吃面线程和做面线程分开就行。这样吃面线程通知的永远是做面线程,做面线程统治的永远是吃面线程。
代码:
1 #include<stdio.h>
2 #include<pthread.h>
3 #include<unistd.h>
4
5 #define THREAD_COUNT 2
6
7 int g_bowl = 0; //0没有面,1有面
8 pthread_mutex_t g_lock; //互斥锁
9 pthread_cond_t g_eat_cond; //条件变量
10 pthread_cond_t g_make_cond;
11
W> 12 void* eat_thread(void* arg){
13 while(1){
14 pthread_mutex_lock(&g_lock);
15 while(g_bowl == 0){
16 printf("我是吃面人,碗里面没面我就不吃了\n");
17 pthread_cond_wait(&g_eat_cond, &g_lock);
18 //pthread_mutex_unlock(&g_lock);
19 //continue;
20 }
21 printf("I am eat thread, eat %d\n", g_bowl--);
22 //解锁
23 pthread_mutex_unlock(&g_lock);
24 //通知做面线程
25 pthread_cond_signal(&g_make_cond);
26 // usleep(1);
27 }
28 }
W> 29 void* make_thread(void* arg){
30 while(1){
31 pthread_mutex_lock(&g_lock);
32 while(g_bowl == 1){
33 printf("我是做面人,碗里面有面,我就不做了。。。\n");
34 pthread_cond_wait(&g_make_cond, &g_lock);
35 //pthread_mutex_unlock(&g_lock);
36 //continue;
37 }
38 printf("I am make thread, make %d\n", g_bowl++);
39 //解锁
40 pthread_mutex_unlock(&g_lock);
41 //通知吃面线程
42 pthread_cond_signal(&g_eat_cond);
43 //usleep(1);
44 }
45 }
46 int main(){
47 //初始化互斥锁
48 pthread_mutex_init(&g_lock, NULL);
49 //初始化条件变量
50 pthread_cond_init(&g_eat_cond, NULL);
51 pthread_cond_init(&g_make_cond, NULL);
52
53 pthread_t eat[THREAD_COUNT], make[THREAD_COUNT];
54 //创建吃面和做面线程
55 for(int i = 0; i < THREAD_COUNT; ++i){
56 int ret = pthread_create(&eat[i], NULL, make_thread, NULL);
57 if(ret < 0){
58 perror("pthread_creat");
59 return 0;
60 }
61 ret = pthread_create(&make[i], NULL, eat_thread, NULL);
62 if(ret < 0){
63 perror("pthread_creat");
64 return 0;
65 }
66 }
67 //主线程等待工作线程
68 for(int i = 0; i < THREAD_COUNT; ++i){
69 pthread_join(eat[i], NULL);
70 pthread_join(make[i], NULL);
71 }
72 //销毁互斥锁
73 pthread_mutex_destroy(&g_lock);
74 //销毁条件变量
75 pthread_cond_destroy(&g_eat_cond);
76 pthread_cond_destroy(&g_make_cond);
77 return 0;