生产者消费者模型多线程队列初学者个人理解

"生产者和消费者"模式 涉及到的问题: 
        
        1. 共享资源的互斥访问 
       缓冲区相当于就是一个缓冲区 多个进程/线程往里面读/取数据,
       涉及到进程/线程间如果有序的访问共享资源的问题。 
            
        我们可以通过信号量/线程互斥锁解决。
    
        2.  另外一点就是当缓冲区满,生产者不能往缓冲区写入数据 或者 当缓冲区为空 消费者就不能从缓冲区读取数据 
            该怎么处理? 
            解决方法有两种: 
            a.不停的测试/循环 看缓冲区里面有没有数据或者缓冲区是否有空间 
             这种方式我们俗称为“轮询”--》轮流询问 代码实现较为简单 
               对于消费者来说: 
                

                while(1)
                {
                    if(缓冲区里面有数据)
                    {
                        //上锁 
                        //从缓冲区读取数据 
                        //读取数据之后处理数据
                        //解锁
                    }
                }
            


    
                对于生成者来说: 
                
          

     while(1)
                {
                    if(缓冲区有空间了)
                    {
                        //上锁
                        //往缓冲区写入数据
                        //解锁
                    }
                }

   那么这种“轮询”会有天生的缺陷 
     效率慢 会有时间差 不及时 浪费CPU等问题
    
     b. 另一种方法当没有数据可以读或者没有空间可以写的时候 
     直接sleep(休眠 让出cpu) 当有数据或者有空间的时候 你通知我(唤醒我)
     这种方式叫做线程条件变量

以下我们以条件变量的方式完成一次(生产者消费者模型创建的多线程队列)

首先要理清什么时候出队什么时候入队,在出队的时候是否要上锁,在入对的时候是否要上锁,什么时候需要让进程休眠等待,理清楚了先后顺序和逻辑后,我们可以开始写代码了。

slqueue.h文件

#ifndef SLQUEUE_H
#define SLQUEUE_H
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#define SLQisEmpty 1
#define SLQisnotEmpty 0
typedef int Elemtype;
typedef struct slque
{
	Elemtype data;
	struct slque* next;
}SLqueue;
 
typedef struct
{
	SLqueue* front;
	SLqueue* rear;
	int size;
}Queue;

Queue* Init_Slqueue();
int IsEmpty(Queue *slqueue);
int Get_slqueue_size(Queue* slqueue);
void Enslqueue(Queue* slqueue,Elemtype x);
Elemtype Deslqueue(Queue *slqueue);
void Clearslqueue(Queue *slqueue);
void Destroyslqueue(Queue *slqueue);
#endif

slqueue.c文件

#include "slqueue.h"

/*
************因为要创建一个全局变量的队列************
    创建一个semlinkedqueue
    多线程队列
    作为生产者消费者模型
*/
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

Queue *Init_Slqueue() //初始化一个Sl队列
{
    Queue *slqueue = (Queue *)malloc(sizeof(Queue));
    slqueue->front = NULL;
    slqueue->rear = NULL;
    slqueue->size = 0;//初始化 使队列的头、尾指针置空,并且大小
    return slqueue;
}

int IsEmpty(Queue *slqueue) //判断sl队列是否为空
{
    if (slqueue->front == NULL && slqueue->rear == NULL && slqueue->size == 0)
        return SLQisEmpty;
    else
        return SLQisnotEmpty;
}

void Enslqueue(Queue *slqueue, Elemtype x) //入队操作
{
    SLqueue* sl = (SLqueue*)malloc(sizeof(SLqueue));
    sl->data = x;
    sl->next = NULL;
    //初始化一个希望入队的结点
    /*
        往sl队列内写操作时 上锁
        写完时 解锁并发送信号

int pthread_cond_signal(pthread_cond_t *cond);
只唤醒一个线程 唤醒处于等待队列中最前面的那个线程

        返回值:
        成功返回0
        失败返回其他值

    */
    pthread_mutex_lock(&mutex);//互斥锁
    if (IsEmpty(slqueue))
        slqueue->front = sl;

    else
        slqueue->rear->next = sl;
    
    slqueue->rear = sl;
    slqueue->size++;

    //执行完入队操作 可以解开锁,并且释放信号唤醒等待队列中的线程
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

    return;
}

Elemtype Deslqueue(Queue *slqueue) //队头元素出队
{
   
    Elemtype res;
    //对堆空间中数据进行操作的时候
    //将上锁,在执行完出队操作后解锁
    pthread_mutex_lock(&mutex);
    while(IsEmpty(slqueue))
    {
        pthread_cond_wait(&cond,&mutex);//只要队列为空,一直阻塞等待直到传递信号被唤醒
    }
    /*printf("slqueue.front.data=%p\n",slqueue->front);
    printf("slqueue.rear.data=%p\n",slqueue->rear);
    printf("slqueue.size=%d\n",slqueue->size);
    */
   if (slqueue->front->next == NULL)//若队列中只有一个元素
    {
        res=slqueue->front->data;
        free(slqueue->front);
        slqueue->rear = NULL;
        slqueue->front = NULL;
    }
    else
    {
        SLqueue* sl = slqueue->front;
        res=sl->data;
        slqueue->front=slqueue->front->next;
        free(sl);
    }
    slqueue->size--;
    pthread_mutex_unlock(&mutex);//解锁
    return res;//返回出队元素的值,方便确认是否正确入队出队
}

void Clearslqueue(Queue *slqueue)
{
    while (!IsEmpty(slqueue))
        Deslqueue(slqueue);
}

void Destroyslqueue(Queue *slqueue)
{
	if(!slqueue)
		return;
	Clearslqueue(slqueue);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	free(slqueue);
	slqueue = NULL;//这一步很重要,将其置空可以避免在结束后仍占有资源的问题
}//在程序完成后,销毁多线程队列,释放其占有的空间

最后,带有验证属性的main.c文件

#include "slqueue.h"


pthread_t producer;//生产者
pthread_t consumer;//消费者
Queue *slqueue=NULL;
void* produce(void* arg)
{
    for(int i =0;i<20;i++)
    {
        Enslqueue(slqueue,20);
    }
}//生产者线程,执行入队操作

void* consum(void*arg)
{
    Elemtype  res=0;
    int runtimes=0;
    while(!IsEmpty(slqueue))
    {
        res=Deslqueue(slqueue);
        printf("res=%d\t",res);
        runtimes++;
        printf("this is the %d time runing\n",runtimes);
    }
}//消费者线程,从内存中读取生产者存入的数据,并将其读出打印

int main(int argc, char const *argv[])
{
    slqueue=Init_Slqueue();
    pthread_create(&producer, NULL, produce,NULL);
    pthread_create(&consumer, NULL, consum,NULL);


    pthread_join(producer, NULL);
    pthread_join(consumer, NULL);//等待两个线程结束并释放其占有的资源


    Clearslqueue(slqueue);
    Destroyslqueue(slqueue);//收尾工作
    return 0;
}

最后,本人还是新手刚入门学习,如果有错误的地方或者说的不够仔细地地方,希望大家可以多多包容,可以在评论区批评指正,让我可以更多的提升自身的知识水平和理解能力。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340