导航

使用信号量实现生产者消费者模型

0、模型介绍
生产者消费者模型可以用自动售货机来类比,往机器里添加待售物品的人员为生产者,从自动售货机获取物品的为消费者,售货机作为物品的缓存。
逻辑如下: 最初自动售货机为空的状态,生产者往机器里面添加物品,当自动售货机放满了物品时,生产值就通知消费者可以购买了,同时生产者
不需要一直在机器旁边等待,可以去休息了。而当消费者不停地从自动售货机里买走物品直到把机器里面的物品都买光后,此时他就要通知生产者
该加东西了,自己也不用一直在边上等待,等物品填装完毕后生产者自然会通知消费者。这是个很简单实用的模型。

1、流程图

生产者

            ---------------------
      |           |
    \|/          |
    \
|---\ 缓存已满? ---否---->添加物品
|   /      |
|           是
|            |
|     通知消费者
|            |
-----等待

 

消费者

              ---------------------
               |                     |
      \|/                           |
    \
|---\ 缓存为空? ---否---->消费物品
|     /        |
|             是
|              |
|       通知生产者
|              |
------等待


2、主要函数介绍
1) pthread_cond_wait(pthread_cond_t * pCond, pthread_mutex_t * pMtx) ;
该函数会在pCond指向的条件变量上等待,直到该条件变量有信号,类似于生产者或者消费者守着某个频段的信号.当pthread_cond_wait进入阻塞等待状态时,会自动解锁pMtx指向的对象.当该函数"醒来"时, 又第一时间获得了锁.

2) pthread_cond_signal(pthread_cond_t * pCond) ;
该函数会把指定的条件变量设置为有信号的状态。

按理来说,pthread_cond_wait 和 pthread_cond_signal 是成对使用的函数,为何pthread_cond_wait额外需要一个同步对象作为参数,而且要求该参数为锁定状态,那是因为只有这样才能保证pthread_cond_wait的调用线程在正确的时间进入到睡眠状态。
举个例子说明,比如上述说到的自动售货机,因为在同一时间,有可能有增加物品、消费物品的操作,在判断售货机是否为空(或者满)状态时,必须要加锁
保护,假设保护该缓存的锁与传递给pthread_cond_wait函数的并不是同一把,可能逻辑如下:

// 定义两把锁
pthread_mutex_t snlLocker ;
pthread_mutex_t bfLocker ;

// 消费者处理逻辑
while (1) {
    bool bufferEmpty ;
    bfLocker.lock(); // 缓存锁用户判断缓存是否为空
    bufferEmpty = true | false;
    bfLocker.unlock();

    // 1

    if (bufferEmpty) {
        snlLocker.lock() ; // 等待锁只单纯为满足函数参数
       pthread_cond_wait(&cond, snlLocker) ;
       snlLocker.unlock() ;
}
}
        

  

上面的代码有什么问题呢,如果线程1先执行到了1处,此时发现缓存是空的状态, 然后其它线程执行,改变了缓存的内容,此时缓存非空, 而后线程1继续得到cpu运行时间,它还认为缓存此时是空的,进入了等待,而此时缓存是非空的。这就造成了一个线程在错误的条件下休眠了。

正确的调用逻辑为:
只定义一把锁

pthread_mutex_t bfLocker ;

// 消费者处理逻辑
while (1) {
    bfLocker.lock() ; 
    while(buffer.isEmpty == false) { // 判断缓存是否为空,用while 而不是if 因为"虚假唤醒"
    pthread_cond_wait(&cond, bfLocker) ; // 缓存为空时进入等待状态,此时才释放缓存锁,保证了唯一性
    }
    bfLocker.unlock() ; // 醒来后立即获得了锁,所以需要释放掉
}

 

所以对于pthread_cond_wait函数参数中的锁应该怎么用,应该用于锁住等待线程和通知线程共同需要访问的联系对象。对于生产者消费者来说,这个共同的对象就是自动售货机

3、代码

#include <stdio.h>
#include <pthread.h>
#include <queue>


pthread_cond_t     g_cond_cs ; // 消费者监听信号
pthread_cond_t     g_cond_pd ; // 生产者监听信号

std::queue<int> g_buffer ;  // 缓存
pthread_mutex_t    g_mtx ;     // 缓存同步对象
bool         g_Exit = false ;

#define MAX_BUFFER_SIZE      1000 // 定义缓存最大容量

// 消费者 
void * Consumer(void * param)
{
    while(!g_Exit) {
        pthread_mutex_lock(&g_mtx) ;    
        while (g_buffer.empty()) { // 判断缓存是否为空
            pthread_cond_signal(&g_cond_pd); // 通知生产者
            //printf("consumer wait\n") ;    
            pthread_cond_wait(&g_cond_cs, &g_mtx) ; 
        }
        // 从缓存中取走一个对象
        g_buffer.pop() ;
    //    printf("consumer remove item, ItemCount:%lu\n", g_buffer.size()) ;
        pthread_mutex_unlock(&g_mtx) ;
    }
    return nullptr ;
}

// 生产者
void * Productor(void * param)
{
    while(!g_Exit) {
        pthread_mutex_lock(&g_mtx) ;    
        while (g_buffer.size() >= MAX_BUFFER_SIZE){
            pthread_cond_signal(&g_cond_cs) ;    
            //printf("productor wait\n") ;
            pthread_cond_wait(&g_cond_pd, &g_mtx);
        }
        // 往缓存中添加物品
        g_buffer.push(int(1)) ;
        //printf("product insert item, itermCount:%lu\n", g_buffer.size());
        pthread_mutex_unlock(&g_mtx) ;
    }
    
    return nullptr ;
}

int main()
{
    // 初始化
    pthread_cond_init(&g_cond_cs, nullptr);
    pthread_cond_init(&g_cond_pd, nullptr);
    pthread_mutex_init(&g_mtx, nullptr) ;

    pthread_t pro1 ;
    pthread_t csmer1;
    pthread_t csmer2;
    pthread_t csmer3;

    // 一个生产者三个消费者
    pthread_create(&pro1, nullptr, Productor, nullptr) ;
    pthread_create(&csmer1, nullptr, Consumer, nullptr) ;
    pthread_create(&csmer2, nullptr, Consumer, nullptr) ;
    pthread_create(&csmer3, nullptr, Consumer, nullptr) ;

    pthread_join(pro1, nullptr) ;
    pthread_join(csmer1, nullptr) ;
    pthread_join(csmer2, nullptr) ;
    pthread_join(csmer3, nullptr) ;

    pthread_cond_destroy(&g_cond_cs) ;
    pthread_cond_destroy(&g_cond_pd) ;
    pthread_mutex_destroy(&g_mtx) ;
    
    return 0 ;
}