上篇我们详细的介绍了一下Linux下线程安全以及锁机制,利用互斥锁实现了线程间的互斥,那么线程同步( 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。生产出来,才能使用,保证临界资源的合理访问)到底该如何实现呢?这就是我和大家接下来要讨论的条件变量。
概念:
条件变量本身不是锁,但是它可以造成线程阻塞。没有资源则等待,生产资源后唤醒等待。条件变量通常搭配互斥锁使用,给多线程提供一个会和的场所。
条件变量操作步骤:
1. 定义条件变量
2.初始化条件变量
3.阻塞等待一个条件变量
4.唤醒条件变量
5.销毁条件变量
条件变量为什么要搭配互斥锁使用:
因为条件变量本身只提供等待与唤醒的功能,具体什么时候等待要用户来进行判断,这个条件的判断通常涉及临界资源的操作,其它线程通常要通过修改条件来促使条件满足,而这个临界的资源的操作应该受到保护,因此要搭配互斥锁一起使用。
条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
概念:POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
实质 : 进化版的互斥锁(1 --> N)
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
信号量操作集函数:
1.定义信号量变量
2.初始化信号量
3.信号量加锁(–操作)
4.信号量解锁(++操作)
5.信号量销毁
引入:生产者与消费者问题是一个著名的问题,它描述的就是一群生产者生产产品,然后让消费者去消费产品的问题。
例如:生产者与消费者的问题就相当于平常的吃面问题,食堂师傅就相当于生产者,而我们的同学就相当于消费者。只有食堂师傅做好面,将面放入碗中,我们才能去吃面,否则我们就只能等待。
生产者消费者模型抽象
如何保证生产者与消费者之间的线程安全
产者与消费者模型实现
条件变量单链表实现
在这里我利用单链表模拟实现生产者与消费者模型,用链表来表示交易场所,一个线程表示生产者,生产者生产数据放入链表中,另一个线程表示消费者,消费者购买产品然后从链表中移除数据。具体实现如下:
1 /*====================
2 * zhang
3 * 生产者消费者模型
4 * 链表存储
5 *===================*/
6
7
8 #include
9 #include
10 #include
11 #include
12
13 //定义链表存储数据
14
15 struct mp{
16 int data;
17 struct mp* next;
18 };
19
20 struct mp* head = NULL;
21 struct mp* cur = NULL;
22
23
24 pthread_mutex_t mutex;
25 pthread_cond_t noodle;
26
27
28
29 //生产者线程入口函数
30 void* productor(void* arg){
31 while(1){
32 //生产者创建数据节点
33 cur = malloc(sizeof(struct mp));
34 cur->data = rand()%1000+1;
35 printf("~~~~produce push a data:%d~~~~\n",cur->data);
36 //放入链表中,加锁
37 pthread_mutex_lock(&mutex);
38 cur->next = head;
39 head = cur;
40 pthread_mutex_unlock(&mutex);
41 //唤醒消费者
42 pthread_cond_signal(&noodle);
43 sleep(rand()%3);
44 }
45 return NULL;
46 }
47
48 //消费者线程入口函数
49 void* consumer(void* arg){
50 while(1){
51 pthread_mutex_lock(&mutex);
52 while(head == NULL){
53 // 阻塞等待数据
54 pthread_cond_wait(&noodle,&mutex);
55 }
56 //获取数据 (删除节点)
57 cur = head;
58 head = cur->next;
59 pthread_mutex_unlock(&mutex);
60 printf("~~~~~~consume a data:%d~~~~\n",cur->data);
61 free(cur);
62 cur = NULL;
63 sleep(rand()%3);
64 }
65 return NULL;
66 }
67
68
69 int main(void){
70
71 pthread_t ptid; //生产者线程ID
72 pthread_t ctid[4]; //消费者线程ID
73 int ret,i;
74 //创建生产者与消费者线程
75 srand(time(NULL));
76 ret = pthread_mutex_init(&mutex,NULL);
77 if(ret != 0){
78 printf("mutex init error!\n");
79 return -1;
80 }
81 pthread_cond_init(&noodle,NULL);
82 if(ret != 0){
83 printf("cond init error!\n");
84 return -1;
85 }
86
87
88 ret = pthread_create(&ptid,NULL,productor,NULL);
89 if(ret != 0){
90 printf("productor create error!\n");
91 return -1;
92 }
93 for(i = 0 ; i < 4; i++){
94 ret = pthread_create(&ctid[i],NULL,consumer,NULL);
95 if(ret != 0){
96 printf("productor create error!\n");
97 return -1;
98 }
99
100 }
101 //回收生产者线程
102 ret = pthread_join(ptid,NULL);
103 if(ret != 0){
104 printf("productor join error!\n");
105 return -1;
106 }
107 //回收消费者线程
108 for(i = 0 ; i < 4; i++){
109 ret = pthread_join(ctid[i],NULL);
110 if(ret != 0){
111 printf("productor join error!\n");
112 return -1;
113 }
114 }
115
116 pthread_mutex_destroy(&mutex);
117 pthread_cond_destroy(&noodle);
118 return 0;
119 }
Posix信号量环形队列实现
利用数组模拟环形队列实现生产者与消费者模型,一个信号量表示数据资源计数器,一个信号量表示空闲空间计数器,如下图:
具体实现如下:
1 /*==================
2 * zhangxu
3 * 利用信号量实现简易版的
4 * 生产者与消费者模型
5 * sem
6 *=================*/
7
8 #include
9 #include
10 #include
11 #include
12 #define NUM 5
13
14 int queue[NUM]; //模拟环形队列
15 sem_t sem_data; //数据资源计数信号量
16 sem_t sem_freespace; //空闲资源计数
17
18
19 //生产者线程入口函数
20 void* productor(void* arg){
21 int i = 0 ;
22 while(1){
23 sem_wait(&sem_freespace);//消耗空间,空闲空间计数信号量-1
24 queue[i] = rand()%1000 + 1;
25 printf("~~~produce a data:%d\n",queue[i]);
26 sem_post(&sem_data); //生产数据,数据资源计数信号量+1
27
28 i = (i + 1) % NUM ;
29 sleep(rand()%3);
30 }
31 return NULL;
32 }
33 //消费者线程入口函数
34 void* consumer(void* arg){
35 int i = 0;
36 while(1){
37 sem_wait(&sem_data); //消耗资源,数据资源计数信号量-1
38 printf("~~~consume a data:%d\n",queue[i]);
39 queue[i] = 0;
40 sem_post(&sem_freespace);//消耗数据,空闲空间计数信号量+1
41
42 i = (i + 1) % NUM;
43 sleep(rand()%3);
44 }
45 return NULL;
46 }
47
48
49
50 int main(void){
51
52 //定义生产者消费者线程
53 pthread_t ptid,ctid;
54 int ret;
55 ret = sem_init(&sem_data,0,0);
56 if(ret == -1){
57 printf("sem_data init error!\n");
58 return -1;
59 }
60 ret = sem_init(&sem_freespace,0,NUM);
61 if(ret == -1){
62 printf("sem_freespace init error!\n");
63 return -1;
64 }
65
66 //创建线程
67 ret = pthread_create(&ptid,NULL,productor,NULL);
68 if(ret != 0){
69 printf("porductor create error!\n");
70 return -1;
71 }
72
73 ret = pthread_create(&ctid,NULL,consumer,NULL);
74 if(ret != 0){
75 printf("consumer create error!\n");
76 return -1;
77 }
78
79 //回收线程
80 ret = pthread_join(ptid,NULL);
81 if(ret != 0){
82 printf("porductor join error!\n");
83 return -1;
84 }
85 ret = pthread_join(ctid,NULL);
86 if(ret != 0){
87 printf("consumer join error!\n");
88 return -1;
89 }
90
91 //销毁信号量
92 ret = sem_destroy(&sem_data);
93 if(ret == -1){
94 printf("sem_data destroy error!\n");
95 return -1;
96 }
97 ret = sem_destroy(&sem_freespace);
98 if(ret == -1){
99 printf("sem_freespace destroy error!\n");
100 return -1;
101 }
102
103 return 0;
104 }