线程同步之 生产者消费者模型详解

前言

       博主本来没打算讲这个比较前面的知识的(博主socket编程还有两个部分没讲,进程也才写完回收僵尸进程的三种方法,信号捕捉器也才完结),但是今天有朋友来问博主,什么是生产者消费者模型,所以博主就先为为数不多的朋友把生产者消费者模型讲一讲,希望大家能看懂(没有现成和锁知识的朋友不要急,这部分是写给有基础的朋友看的,这些知识博主都会慢慢的讲到)。

什么是模型?

模型就是要解决某个问题的固定方法或套路,所以有时候我们学会一点模型是很有必要的。

生产者消费者条件变量模型

       线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
       换一句话来说就是,比如说你在学校上课,好不容易熬到了放学,此时你肯定会和你的小伙伴一起去食堂吃饭(土豪除外,土豪不去食堂),但是因为之前食堂停水停电,导致,你们放学了才开始做饭,所以现在就会面临一个问题。
       当食堂大叔做好饭之后会把饭放在指定的饭盒里面,但是食堂大叔做饭的速度可能比不上你和你小伙伴吃饭的速度在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述(或者大叔做饭的速度太快导致们根本吃不过大叔的做饭速度)。这也就是我们的生产者消费者模型了。

所以博主先列出一些概念,更容易使大家理解:

生产者:产生数据的一方(食堂大叔做饭)
消费者:处理数据的一方(你和你的小伙伴吃饭)

我们会遇到的问题

双方处理数据的速度可能不同,这样就会导致总由一方吃处于阻塞状态。(也就是博主上面所说的吃饭的问题)可能博主的逻辑不是很严谨,但是将就着看吧。

要用到的函数解析:

int pthread_cond_wait

 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
 
函数作用:
1.阻塞等待条件变量cond(参1)满足	
2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
 1.2.两步为一个原子操作。
3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);


我们要知道的是在调用这个函数之前我们要做前准备好的事情:
1.mutex_t mutex 定义互斥量
2.init 初始化
3.lock 加锁
4.cond_t has_product 定义条件变量
5.inti	初始化

pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);

唤醒至少一个阻塞在条件变量上的线程

提到了这么多的东西了,接下来博主就给出生产者消费者模型的代码(里面有很详细的解释了,大家应该都能看懂,博主就不在做GIF动画了)

/*借助条件变量模拟 生产者-消费者 问题*/
#include 
#include 
#include 
#include 

/*链表作为公享数据,需被互斥量保护*/
struct msg {
    struct msg *next;
    int num;
};

struct msg *head;
struct msg *mp;

/* 静态初始化 一个条件变量 和 一个互斥量  完成 1 2   4 5 */
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    while(true){
        pthread_mutex_lock(&lock);	//加锁  完成 3 
        while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗
            pthread_cond_wait(&has_product, &lock);		//此时会阻塞等待 等生产者传入之后才会继续工作
				/*	1.阻塞等待条件变量cond(参1)满足	
					2.释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
						注意: 	1和2这两步为一个原子操作。
					3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);*/
        }
       
		mp = head;      
        head = mp->next;    //模拟消费掉一个产品
        pthread_mutex_unlock(&lock);	//解锁

        printf("-Consume ---%d\n", mp->num);
        free(mp);	//create 和 free 要成双成对出现
        mp = NULL;	

        sleep(rand() % 5);	//随机随眠让 CUP 转移
    }
}

void *producer(void *p)
{
   while(true) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品

        printf("-Produce ---%d\n", mp->num);

        pthread_mutex_lock(&lock);	//加锁(不用担心死锁,即使是消费者先调用,在pthread_cond_wait()之后也会将之前获得的锁给释放!
			//头插法
		mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);	//解锁

        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程(生产者线程)唤醒
        sleep(rand() % 5);	//随机随眠让 CUP 转移
    }
}

int main(int argc, char *argv[])
{
    pthread_t pid, cid;	//pid-->生产者    cid -->消费者
    srand(time(NULL));

	/* 调用各自的主控函数 */
    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

	//回收各自的线程!
    pthread_join(pid, NULL);
    pthread_join(cid, NULL);

    return 0;
}

有朋友会遇到这种问题:
我为什么写的不能实现同步呢?就只是一个生产者在那干活
博主认为最有可能的原因是因为时序竞态,这种解决办法很简单加sleep就行了。
或者是 博主上面的两个函数没使用好导致这种情况,要记住 wait等的是生产的信号,signal发的是生产者的信号去唤醒阻塞的消费者。

你可能感兴趣的:(网络编程,线程)