简单的生产者和消费者模型——互斥锁和条件变量实现
先来简单的阐述一下生产者和消费者模型,这个模型描述了这么一个场景:内存中有一块缓冲区叫做仓库,生产者可以向里面放数据,消费者可以从里面取数据。那么此时需要注意的就是:生产者往仓库生产东西的时候,消费者不能那从仓库取东西,同样的道理,消费者从里面取东西的时候,生产者不能往里面放东西,而且,仓库为空的时候,消费者不能消费,仓库满的时候,生产者不能生产者。总结一下那就是生产者消费者模型的321原则,3种关系,2个角色,1个消费场所:
3种关系:生产者和消费者之间(互斥和同步)生产者之间(互斥)消费者之间(互斥)
2个角色:生产者和消费者
1个消费场所:也就是刚才例子中的仓库,在操作系统中有可能是链表等数据结构
互斥锁:互斥锁在我的上一篇博客:点击打开链接,有很详细的讲解,此处就不做过多的解释,总而言之,互斥锁对临界资源进行
了保护。使得临界资源具有互斥的属性
条件变量:通常情况下条件变量和互斥锁配合使用,用来标记临界资源的状态
那我们既然要模拟消费者生产者模型,就要有一个消费场所,我们使用链表来模拟消费场所,生产者就是往链表里面放结点,消费
者就从链表里面取结点,这就是我们需要模拟的场景,而我们使用到的互斥锁的创建和条件变量的创建,具体如下:
代码如下:
#include
#include
#include
typedef struct Node{
int _data;
struct Node* _next;
}Node,*PNode,**PPNode;
PNode head;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static PNode alloc_node(int data)
{
PNode tmp = (PNode)malloc(sizeof(Node));
if(!tmp)
{
perror("look malloc");
exit(1);
}
tmp->_data = data;
tmp->_next = NULL;
return tmp;
}
int isEmpty(PNode h)
{
if(h->_next)
return 1;
return 0;
}
static void free_node(PNode p)
{
if(p)
free(p);
}
void InitList(PPNode h)
{
*h = alloc_node(0);
}
void PushFront(PNode h,int data)
{
PNode _n = alloc_node(data);
_n->_next = h->_next;
h->_next = _n;
}
void PopFront(PNode h,int* out)
{
if(isEmpty(h) == 1){
PNode p = h->_next;
h->_next = p->_next;
*out = p->_data;
free_node(p);
}
}
void ShowList(PNode h)
{
PNode start = h->_next;
while(start){
printf("%d ",start->_data);
start = start->_next;
}
printf("\n");
}
void DestoryList(PNode h)
{
int out;
while(isEmpty(h) == 1)
{
PopFront(h,&out);
}
free_node(h);
}
void* product(void* arg)
{
while(1){
int data = rand()%1234;
pthread_mutex_lock(&lock);
PushFront(head,data);
pthread_mutex_unlock(&lock);
printf("product done:%d\n",data);
sleep(1);
}
}
void* consumer(void* arg)
{
while(1){
int data = -1;
pthread_mutex_lock(&lock);
PopFront(head,&data);
pthread_mutex_unlock(&lock);
printf("consumer done:%d\n",data);
}
}
int main(){
InitList(&head);
pthread_t c,p;
pthread_create(&p,NULL,product,NULL);
pthread_create(&c,NULL,consumer,NULL);
pthread_join(p,NULL);
pthread_join(c,NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
但是此时我们运行结果发现,消费者一直在取结点,但是链表里面没有结点,所以一直循环打印-1,原因就是我们在生产者的
代码里面让他休息了一秒,所以当消费者去取结点的时候是没有结点可以取的,但是消费者就一直的访问临界资源,但是由于
消费者一直访问临界资源,那么此时生产者就没法访问临界资源,也就没法往临界资源里面放结点了,此时消费者访问就没有
结点,打印的就是-1;
此时互斥锁保证了线程访问资源的原子性,但是并不能保证临界资源的状态,此时就要用我们刚才定义到的但是没有用到的
条件变量,代码要改动的部分只有消费者生产者线程要执行的函数:product和consumer
改动后的代码:
void* product(void* arg)
{
while(1){
int data = rand()%1234;
pthread_mutex_lock(&lock);
PushFront(head,data);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
printf("product done:%d\n",data);
sleep(1);
}
}
void* consumer(void* arg)
{
while(1){
int data = -1;
pthread_mutex_lock(&lock);
if(isEmpty(head)==0){
printf("no data,consumer wait...\n");
pthread_cond_wait(&cond,&lock);
}
PopFront(head,&data);
pthread_mutex_unlock(&lock);
printf("consumer done:%d\n",data);
}
}
代码中当消费者申请锁资源之后,判断链表,也就是临界资源是否有值,如果没有值,那就等待,使用pthread_cond_wait接口,
该接口的第一个参数代表使用的条件变量,第二个接口代表该线程使用的锁,既然要等待,就要把该线程使用的锁资源先去
释放掉,然后进入睡眠状态,等待被唤醒,然后,当生产者生产出结点且释放了对资源的控制,此时使用pthread_cond_signal
接口来唤醒刚才睡眠的线程,里面的传的参数就是刚才睡眠线程使用的条件变量,此时刚才睡眠的线程接着自己刚才被睡眠的
程序,继续运行,这样就不会发生刚才那样的情况,当消费者取不到节点的时候,被睡眠掉,当生产者生产出结点的时候,在
把刚才被睡眠的线程唤醒,继续执行。
以上就是我们使用互斥锁和条件变量实现的单消费者和单生产者的生产模型。
限于编者水平,文章难免有缺漏之处,欢迎指正!
如需转载