生产者消费者模型(一)——模拟单线程的互斥与同步

在现实的软件开发过程中,经常会碰到如下情景:某个模块负责产生数据,这些数据由另外一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程和进程等)。产生数据的模块称之为生产者,而处理数据的模块就是消费者。

条件变量

在正式开始生产者与消费者模型之前,我们应该对条件变量有一个新的认识。

条件变量(Condition Variable):它的作用是描述资源的就绪状态,属于线程的一种同步机制。互斥锁用于上锁,条件变量用于等待。条件变量本身是由互斥量保护的,线程在改变之前首先会封锁互斥量,因此其他线程在获得互斥量之前不会觉察到这样的变化。

(1)pthread_cond_init 和 pthread_cond_destroy:条件变量的初始化与销毁。

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);//条件变量销毁
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);//条件变量初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

参数cond:条件变量。

参数attr:条件变量的属性,设置为NULL则表示缺省属性。

返回值:成功返回0,失败返回错误码。

和mutex的初始化和销毁类似,如果条件变量是静态分配的,也可以使用PTHREAD_COND_INITALIZER初始化,相当于用pthread_cond_init函数初始化并设置参数attr为NULL。

(2)pthread_cond_wait/timewait:前者是条件变量等待,后者是等待超时。

 #include <pthread.h>
 int pthread_cond_timedwait(pthread_cond_t *restrict cond,  pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

参数cond:条件变量。

参数mutex:互斥锁。

返回值:成功返回0,失败返回错误码。

pthread_cond_timewait函数还有一个额外的参数可以设定等待超时,如果达到了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。

可见,一个条件变量总是和一个互斥锁搭配使用的。

一个线程可以调用pthread_cond_wait在一个条件变量上阻塞等待,这个函数需要以下三步操作:

1)释放mutex    2)阻塞等待    3)当被唤醒时,重新获得mutex并返回

(3)pthread_cond_broadcast/signal:唤醒线程。前者是唤醒进程内的所有线程,后者是唤醒一个线程。

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

返回值:成功返回0,失败返回错误码。

一个线程可以调用pthread_cond_signal唤醒在某个条件变量上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个条件变量上等待的所有线程。

生产者消费者模型

生产者与消费者牵扯最多的是多线程的同步问题,单单抽象出生产者和消费者,还够不上生产者消费者模型。因此该模式需要加入一个缓冲区作为二者的媒介,生产者把数据放入缓冲区,消费者从缓冲区取走数据,结构图如下:

生产者与消费者模型的321规则:3种关系,2个角色,一个交易场所(本文使用链表模拟实现)

这里我简单说一下三种关系:

(1)生产者与生产者(互斥)

(2)生产者与消费者(互斥+同步)

(3)消费者与消费者(互斥)

也就是说,生产者在进行生产的时候消费者不能消费,消费者在消费的时候生产者同样不能生产。缓冲区为空时,消费者不能消费,缓冲区满时,生产者也不能生产。

生产者消费者模型的三大特性

(1)解耦(软件工程追求高内聚低耦合)

假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(耦合)。将来如果消费者的代码发生了变化,可能会影响到生产者。但是如果二者均依赖于同一个方法(缓冲区),耦合度就会降低很多。

(2)支持并发(最主要的特性)

由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只能一直等待,如果消费者的处理速度很慢,生产者将会浪费很多时间。因此,生产者消费者模型就提出了缓冲区的概念,即生产者将数据放入缓冲区后,可以继续生产,将不再依赖于消费者的消费速度。

(3)支持忙闲不均

缓冲区的另一个好处在于生产数据的速度时快时慢,当数据生产的较快的时候,如果消费者来不及处理,未处理的数据就可以存放在缓冲区,消费者再慢慢进行处理。

基于单线程的生产者消费者模型

我们使用两个线程分别模拟了生产者和消费者,底层的数据结构借助于链表作为缓冲区,生产者通过向链表插入节点,消费者从链表中删除节点来模拟生产者与消费者的行为。

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//定义互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//定义条件变量

typedef struct list
{
  int _data;
  struct list* _next;
}Node,*pNode;

typedef struct linklist
{
  Node *phead;
}linklist,*plinklist;

pNode CreatNode(int data)
{
  pNode newNode = (pNode)malloc(sizeof(Node));
  if(newNode == NULL)
  {
    perror("malloc");
    exit(1);
  }
  newNode->_data = data;
  newNode->_next = NULL;
  return newNode;
}
void InitList(plinklist plist)
{
  assert(plist);
  plist->phead = NULL;
}
void Push(plinklist list, int data)
{
  assert(list);
  pNode newNode = CreatNode(data);
  if(list->phead == NULL)
  {
    list->phead = newNode;
    return;
  }
  newNode->_next = list->phead;
  list->phead = newNode;
}

void Pop(plinklist list,int *data)
{
  assert(list);
  if(list->phead == NULL)
  {
    printf("list is empty!\n");
    return;
  }
  pNode pDel = list->phead;
  *data = pDel->_data;
  list->phead = pDel->_next;
  pDel->_next = NULL;
  free(pDel);
  pDel = NULL;
}
void Destroy(plinklist list)
{
  assert(list);
  if(list->phead != NULL)
  {
    pNode pCur = list->phead;
    while(pCur)
    {
      pNode pDel = pCur;
      pCur = pCur->_next;
      free(pDel);
      pDel = NULL;
    }
  }
  list->phead = NULL;
}
void *producter(void *arg)//生产者
{
  plinklist list = (plinklist)arg;
  while(1)
  {
    sleep(1);
    pthread_mutex_lock(&lock);
    Push(list,rand() % 100 + 1);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&lock);
    printf("producter : %d\n",list->phead->_data);
  }
}

void *consumer(void *arg)//消费者
{
  plinklist list = (plinklist)arg;
  while(1)
  {
    sleep(1);
    pthread_mutex_lock(&lock);
    int data = 0;
    while(list->phead == NULL)
    {
      pthread_cond_wait(&cond, &lock);
    }
    Pop(list, &data);
    pthread_mutex_unlock(&lock);
    printf("consumer : %d\n",data);
  }
}
int main()
{
  linklist list;
  InitList(&list);
  pthread_t tid1,tid2;
  
  pthread_create(&tid1, NULL, producter, (void*)&list);
  pthread_create(&tid2, NULL, consumer, (void*)&list);
 
  pthread_join(tid1, NULL);
  pthread_join(tid2, NULL);
 
  Destroy(&list);
  return 0;
}

运行结果如下:

从结果可以看出,生产者生产一个,消费者消费一个,循环交替,实现了基于单线程的同步与互斥的生产者消费者模型。



猜你喜欢

转载自blog.csdn.net/ypt523/article/details/80373032
今日推荐