缓冲区机制(生产者-消费者模式)

生产者、消费者模式是我们在编程时经常用到的一个编程方法。
image-20210707103528411

生产者是生产数据,提供原料的一方;消费是消耗数据,处理原料的一方。


缓冲区的意义

缓冲区一般是充当一个数据中转站的作用,使得生产数据和处理数据双方解耦合,各自分工。根据生产者和消费者的能力,进行均衡调整,使得系统高效配合工作。

电脑中的缓冲例子:当CPU需要去读取磁盘数据时,因为CPU和磁盘的读写速度相差几个数量级,这样会导致CPU处于空等待状态,所以采用了一个缓冲机制,先将磁盘数据放到缓冲区,当数据读取完成后,再通知CPU从缓冲中取数据。从而避免了CPU的无效等待,提高系统效率。


供求关系

缓冲区的大小是有限的,如果生产速度和消费速度不平衡,则会逐渐走向极端(缓冲区常空或常满)。

1)供 > 求(生产能力强),缓冲区数据逐渐堆积,最终生产者闲置;

2)供 < 求(消费能力强),缓冲区数据消耗快,最终消费者闲置;

3)供 = 求,生产和消耗速度达到平衡,缓冲区的数据量会稳定在一个范围。

供求平衡是我们追求的较理想的状态,但实际中生产能力和消费能力往往是存在差异的,需要我们根据实际的问题进行优化。例如如果生产能力过剩,则我们可以增加消费者线程,或抑制生产能力。(看到这里其实不难想到,该模式与生活中的市场问题是类似的,处理市场供求关系的一些手段也可以用于管理缓冲区)


在无法达到平衡时,该如何取舍?

让消费速度大于生产速度,虽然消费者会闲置,但是可以减少数据滞留时间。


多样的生产-消费关系

我们在使用多线程对这类关系进行编码,可以是只有一个生产者线程,一个消费者线程;也可以是一个生产者线程,同时有多个消费者线程处理数据。以及其它多生产多消费的复杂场景。(缓冲区只有一个)
image-20210707103032837

甚至于有时候,一个线程可以是当前任务的消费者,也是下一个任务的生产者。多个线程组成一个生产-消费关系的链条,类似工厂流水线(组装 — 测试 — 包装)。
image-20210706211720222

在生产消费者模式链中,制约系统运行效率的往往是处理最慢的那个环节,将每个环节比喻为一个个管道,多个小管道拼接成为一个长的管道,水流速度往往取决于最窄也就是速度最慢的那一截管道。有点类似木桶效应。所以我们平时在处理优化问题时,应该找到制约效率的关键位置,然后针对性的处理,才能大大提高系统效率。


线程池编程实例

现在有一个任务:找出LEFT~RIGHT范围内的质数有哪些,并打印出来。

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

#define LEFT    30000000
#define RIGHT   30000200
#define THRNUM  (4)

// 线程池实现,main线程分发数字(任务),>0为有任务,=0当前任务被领取,=-1没有任务可以分发了;
// 其它线程抢任务,计算完成后将num清0;如果num为-1, 线程跳出循环结束。
static pthread_mutex_t mut_num = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_num = PTHREAD_COND_INITIALIZER;
static int num = 0;

static void* thr_primer(void *p)
{
    int i,j,mark;
    int n = (int)p;

    while(1)
    {
        pthread_mutex_lock(&mut_num);	//抢锁
        while(num == 0)
        {
            pthread_cond_wait(&cond_num,&mut_num);	//等待任务下发或结束
        }
        if(num == -1)
        {
            pthread_mutex_unlock(&mut_num);	//解锁
            break;
        }
        i = num;
        num = 0;
        pthread_cond_broadcast(&cond_num);	//广播通知任务被领取
        pthread_mutex_unlock(&mut_num);		//解锁
        
		// 领到数字,判断是否是质数
        mark = 1;
        for(j = 2; j < i/2; j++)
        {
            if(i % j == 0)
            {
                mark = 0;
                break;
            }
        }
        if(mark)
            printf("[%d]:%d is primer\n",n,i);
    }
    pthread_exit(NULL);
}

int main()
{
    pthread_t tid[THRNUM];
    int err,i;

    for(i = 0; i < THRNUM; i++)	// 创建线程池
    {
        err = pthread_create(tid+i,NULL,thr_primer,(void *)i);
        if(err)
        {
            fprintf(stderr,"pthread_create():%s\n",strerror(err));
            exit(1);
        }
    }

    for(i = LEFT; i <= RIGHT; i++)
    {
        pthread_mutex_lock(&mut_num);	
        while(num != 0)
        {
            pthread_cond_wait(&cond_num,&mut_num); //等待任务被线程领取
        }
        num = i;
        pthread_cond_signal(&cond_num);	//通知下游线程,有任务分发下来

        pthread_mutex_unlock(&mut_num);	//解锁    
    }

    pthread_mutex_lock(&mut_num);		//抢锁
    num = -1;	//提醒线程结束
    pthread_cond_broadcast(&cond_num);	//通知下游所有线程退出

    pthread_mutex_unlock(&mut_num);    

    //回收线程
    for(i = 0; i < THRNUM; i++)
    {
        pthread_join(tid[i],NULL);
    }
    pthread_mutex_destroy(&mut_num);
    pthread_cond_destroy(&cond_num);

    exit(0);
}

其它:stdio中的三种缓冲模式


有问题欢迎私信或留言讨论,如果觉得内容还可以的话麻烦点赞支持一下。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值