我们在操作系统中讲到进程同步的问题的时候,都会讲一些经典的例子,其中就有“生产者和消费者的问题”。生产者和消费者的规则是生产者生产一个产品后,消费者才能消费,并且在消费者还没有消费已经生产的产品的时候,生产者是不能再进行生产的。先说一下互斥量和条件变量。
1、mutex (互斥量)
多个线程同时访问共享数据时可能会冲突,这跟前⾯面讲信号时所说的可重入性是同样的问 题。比如:两个线程都要把某个全局变量增加1,这个操作在某平台需要三条指令完成: (非原子操作)
1. 从内存读变量值到寄存器
2. 寄存器的值加1
3. 将寄存器的值写回内存
下列程序运行每次结果不同。
Mutex用pthread_mutex_t类型的变量表示,可以这样进行初始化和销毁。
返回值:成功返回0,失败返回错误号。
pthread_mutex_init函数对Mutex做初始化,参数attr设定Mutex的属性,如果attr为NULL则表示缺省属性。用 pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量
是静态分配的(全局变量 或static变量),也可以
用宏定义PTHREAD_MUTEX_INITIALIZER 来初始化,
相当于用pthread_mutex_init初始化并且attr参数为NULL。
Mutex的加锁和解锁
操作可以用下列函数:
返回值:成功返回0,失败返回错误号。
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用 pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用 pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,
如果Mutex已 经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。故利用互斥锁更改上述程序,如下所示:
达到预想效果,结果为10000
。
左边的程序存在的问题:对Mutex变量的读取、判断和修改不是原子操作。如果两个线程 同时调用lock,这时Mutex是1,两个线程都判断mutex>0成立,然后其中一个线程置mutex=0,而另一个线程并不知道这一情况,也置mutex=0,于是两个线程都以为自己获得了锁。
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。unlock中的释放锁操作同样只用一条指令实现,以保证它的原子性。故将左边的程序改成右边的程序。
2、条件变量(Condition Variable)
在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个 条件的线程。Condition Variable用pthread_cond_t类型的变量表示,可以这样初始化和销毁:
返回值:成功返回0,失败返回错误号。
和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为 NULL,则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果Condition Variable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用 pthread_cond_init函数初始化并且attr参数为NULL。Condition Variable的操作可以用下列函数
返回值:成功返回0,失败返回错误号。
可见一个条件变量总是和一个互斥锁搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回
pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指 定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用 pthread_cond_signal唤醒在某个Condition Variable上等待的另一个线程,也可以调用 pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程。
Mutex变量是非0即1的,可看作一种资源的可用数量,初始化时Mutex是1,表示有一个可用资源, 加锁时获得该资源,将Mutex减到0,表示不再有可用资源,解锁时释放该资源,将Mutex重新加 到1,表示又有了一个可用资源。信号量(Semaphore)和Mutex类似,表⽰示可⽤用资源的数量,和Mutex不同的是这个数量可以大于1。
可以通过Mutex和Condition Variable实现生产者-消费者问题,也可以用信号量实现生产者-消费者问题,下面利用互斥锁和条件变量进行演示
生产者和消费者问题的例子:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<pthread.h>
4
5 typedef struct List
6 {
7 struct List *next;
8 int val;
9 }phead;
10
11 phead* head = NULL;//头节点
12 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
13 static pthread_cond_t need_product = PTHREAD_COND_INITIALIZER;
14
15 void init_list(phead* list)
16 {
17 if(NULL != list)
18 {
19 list->next = NULL;
20 list->val = 0;
21 }
22 }
23
24 void* consumer(void* val)
25 {
26 phead* cur = NULL;
27 while(1)
28 {
29 sleep(6);
30 pthread_mutex_lock(&lock);
31 while(NULL == head)
32 {//为空,进行阻塞等待
33 pthread_cond_wait(&need_product,&lock);
34 }
35 // //尾删-----------只能进行
36 // cur = head;
37 // if(cur->next == NULL){
38 // free(head);
39 // head = NULL;
40 // }
41 // else{
42 // while(cur->next->next != NULL)
43 // cur = cur->next;
44 // printf("consum success, val is: %d\n",cur->next->val);
45 // free(cur->next);
46 // cur->next = NULL;
47 // }
48 //头删
49 cur = head;
50 head = head->next;
51 cur->next = NULL;
52 pthread_mutex_unlock(&lock);
53 printf("consum success, val is: %d\n",cur->val);
54 free(cur);
55 cur = NULL;
56 }
57 return NULL;
58 }
59
60 void* product(void* val)//头插
61 {
62 int i = 0;
63 while(1)
64 {
65 sleep(1);//生产时间比消费时间长或相等,会生产一个立刻消费一个;否则> ,就消费最近/远数据的
66 phead *cur = malloc(sizeof(phead));//建立节点
67 pthread_mutex_lock(&lock);
68 init_list(cur);//初始化
69 cur->val = rand()%4321;
70 cur->next = head;
71 head = cur;
72 pthread_mutex_unlock(&lock);
73 printf("call consumer!product success,val is: %d\n",cur->val);
74 pthread_cond_signal(&need_product);//插入后,发送信号
75 }
76 }
77
78 int main()
79 {
80 pthread_t t_product1;
81 pthread_t t_product2;
82 pthread_t t_product3;
83 pthread_t t_consumer1;
84 pthread_t t_consumer2;
85 pthread_t t_consumer3;
86 pthread_create(&t_product1,NULL,product,NULL);
87 pthread_create(&t_product2,NULL,product,NULL);
88 pthread_create(&t_product3,NULL,product,NULL);
89 pthread_create(&t_consumer1,NULL,consumer,NULL);
90 pthread_create(&t_consumer2,NULL,consumer,NULL);
91 pthread_create(&t_consumer3,NULL,consumer,NULL);
92
93 pthread_join(t_product1,NULL);
94 pthread_join(t_product2,NULL);
95 pthread_join(t_product3,NULL);
96 pthread_join(t_consumer1,NULL);
97 pthread_join(t_consumer2,NULL);
98 pthread_join(t_consumer3,NULL);
99 return 0;
100 }
运行结果如下: