什么是生产者消费者模型
生产者消费者模型可以总结为123
1指的是一种交易场所
交易场所就是指临界区,临界区是在同一时刻只可以有一个进程访问的资源
2指的是两种角色
- 消费者
- 生产者
3指的是有三种关系
-生产者与生产者(互斥)
-生产者与消费者(互斥+同步)
-消费者与消费者(互斥)
具体实现代码
#include<stdio.h>
#include<signal.h>
#include<pthread.h>
#include<stdlib.h>
//////////////////////////
//1.构造交易场所
//////////////////////////
typedef struct Node
{
int data;
struct Node* next;
}Node;
//不在函数体内用free 有两点原因
//1.为了好看
//2.为了复用 日好好维护
void DestroyNode(Node* delnode)
{
free(delnode);
delnode = NULL;
}
Node* CreateNode(int value)
{
Node* newnode = (Node*) malloc(sizeof(Node));
newnode->data = value;
newnode->next = NULL;
return newnode;
}
void Init(Node** head)
{
*head = CreateNode(-1);
}
void Push(Node* head,int value)
{
Node* newnode = CreateNode(value);
newnode->next = head->next;
head->next = newnode;
}
void Pop(Node* head,int* value)
{
if(head->next==NULL)
return;
Node* delnode = head->next;
head->next = delnode->next;
*value = delnode->data;
DestroyNode(delnode);
}
Node* g_head =NULL;
////////////////////////////////////////
//2.构造两种角色
///////////////////////////////////////
void* Product(void* arg)
{
(void) arg;
int count = 0;
while(1)
{
pthread_mutex_lock(&g_lock);
Push(g_head,++count);
printf("Product %d\n",count);
pthread_cond_signal(&g_cond);
pthread_mutex_unlock(&g_lock);
usleep(678123);
}
return NULL;
}
void* Consum(void* arg)
{
(void) arg;
int value = 0;
while(1)
{
pthread_mutex_lock(&g_lock);
while(g_head->next ==NULL)
{
pthread_cond_wait(&g_cond,&g_lock);//原子操作
}
Pop(g_head,&value);
printf("Consume %d\n",value);
pthread_mutex_unlock(&g_lock);
usleep(123456);
}
return NULL;
}
//三种关系通过条件变量和互斥变量进行规范
pthread_cond_t g_cond;
pthread_mutex_t g_lock;
int main()
{
pthread_cond_init(&g_cond,NULL);
pthread_mutex_init(&g_lock,NULL);
Init(&g_head);
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,Product,NULL);
pthread_create(&tid2,NULL,Consum,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_cond_destroy(&g_cond);
pthread_mutex_destroy(&g_lock);
return 0;
}
分析
- 在这里我们特别需要分析的是怎样做到对临界区访问保持的这三种关系,我们可以将目标继续细化,其实就是分析pthread_mutex_lock(),pthread_mutex_unlock(),pthread_cond_signal(), pthread_cond_wait()这四个个function,前两个函数也是非常好理解的,比如说我们现在要开保险柜,保险柜只有一把钥匙,假如小明拿到了钥匙,那么就只有小明才可以打开保险柜,小红要想打开是完全不可能的,因为她没有钥匙,可是小红也想打开怎么办,这个时候就需要小明将钥匙放回原处,这时候小红才可以拿到钥匙,总的来说就是这个钥匙实现了小红和小明只有一个才可以打开保险柜这件事情。同样的我们的线程就相当于小红,小明,线程要想实现互斥访问临界区,就必须在进入临界区之前先加锁,处理完后,释放锁,这就是pthread_mutex_lock(),pthread_mutex_unlock()做的事情,加锁和释放锁。
- 现在所有的互斥关系我们通过互斥变量实现了,还剩下一种最重要的关系,那就是同步关系,同步指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系,在我们这个模型也就是说,只有生产者首先生产了,消费者才可以去访问临界区进行消费。如何进行实现呢,这就引入了我们的pthread_cond_signal()和pthread_cond_wait()。
- pthread_cond_signal()就是将条件已经成熟的信号告诉给等待线程,现在已经告诉给等待线程了,那pthread_cond_wait()这边是如何进行回应的呢?
- pthread_cond_wait()内部机制,看上面代码,我们会发现如果条件不满足,pthread_cond_wait()会阻塞自己,但是它持有的锁怎么办呢?如果不归还,那么其他线程也将无法访问公有资源,这就要探究一下pthread_cond_wait()内部实现机制,当pthread_cond_wait()被调用线程阻塞时候,pthread_cond_wait()会自动释放互斥锁,释放互斥锁的时机是线程从调用pthread_cond_wait()到操作系统把它放在线程队列之后,这样做重要原因是,就是互斥变量的作用,保护条件。我们可以假设,线程是并发执行的,如果在没有把阻塞的线程A放在等待队列之前就释放锁,这意味着其他线程可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,导致A忽略等待条件被满足的信号。倘若在线程A调用pthread_cond_wait()开始,都持有互斥锁,其他线程无法得到互斥锁,就不能改变公有资源,这就保证了线程A被放在等待队列之后才会有公有资源被改变的信号传递给等待队列,最后一点要说明的就是必须要循环进行等待, 否则可能出现条件变量wait失败的情况(wait会被信号打断)
总结
分解pthread_cond_wait的动作为以下几步:
1,线程放在等待队列上,解锁
2,等待 pthread_cond_signal或者pthread_cond_broadcast信号之后去竞争锁
3,若竞争到互斥索则加锁
。
生产者消费者模型的三大特性
▶解耦(软件工程追求高内聚、低耦合)
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(耦合)。将来如果消费者的代码出现变化,可能会影响到生产者。但是如果二者均依赖于同一个方法(缓冲区),耦合自然就降低了。
▶支持并发(最主要的特性)
由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只能一直等待,如果消费者的处理速度很慢,生产者则会浪费很多时间。因此,生产者消费者模型就提出了缓冲区的概念,即生产者将数据放入缓冲区后,可以继续生产,将不再依赖于消费者的处理速度。
▶支持忙闲不均
缓冲区的另一个好处在于,生产数据的速度时快时慢,当数据制造快的时候,如果消费者来不及处理,未处理的数据就可以存放进缓冲区,消费者再慢慢处理。