【linux系统编程】线程同步+基于BlockingQueue的生产者消费者模型

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前在阿里

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以点击这里获取!

    while (true)
    {
        sleep(1);
        // 一秒唤醒一个线程
        //pthread\_cond\_signal(&cond);
        //唤醒一批线程
        pthread\_cond\_broadcast(&cond);
        std::cout << "main thread wakeup one thread..." << std::endl;
    }

在这里插入图片描述

现在我们左手有生产者消费者模型,右手有互斥和同步,接下来我们怎么把它们结合在一起呢?
所以我们接下来写一份基于BlockingQueue的生产者消费者模型

3.基于BlockingQueue的生产者消费者模型

BlockingQueue是一个阻塞队列,首先它是一个队列,既然是一个队列就有为空的情况,同时我们对队列设定一个上限。这时当队列为满为空就要约束生产和消费应该阻塞住不应该在生产和消费了。这种我们就称之为BlockQueue。

未来生产者一定是向BlockQueue里放数据,此时BlockQueue就是一段特定结构的缓冲区,消费者一定是向BlockQueu里取数据。

在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞)

在这里插入图片描述
要写生产者和消费者模式必须满足321原则,不过我们刚开始学,我们先写单生产者和单消费者维护它们之间的互斥和同步关系。后面代码写完我们在推而广之变成多生产者和多消费者。

3种关系,先写单生成,单消费
2种角色,生产者线程,消费者线程
1个交易场所,BlockQueue阻塞队列

站在编程的角度,线程A往队列中放,线程B往队列中拿。这个队列就是两个线程的共享资源。线程A放的时候线程B不能拿,线程B拿的时候线程A不能放。队列满的时候生产者线程A就不能生产了,要想办法让线程A去等待,队列空的时候消费者线程B也不能拿了,也要想办法让线程B去等待。因此我们所学互斥锁和条件变量都是需要的。
今天我们直接用C++的queue充当我们的阻塞队列。

上层调用逻辑大的框架我们先写出来

#include"BlockQueue.hpp"

void\* productor(void\* args)
{
    BlockQueue<int>\* bq=static\_cast<BlockQueue<int>\*>(args);

    while(true)
    {
        //生产活动,不断向阻塞队列中放
    }
    return nullptr;

}

void\* consumer(void\* args)
{
    BlockQueue<int>\* bq=static\_cast<BlockQueue<int>\*>(args);
    while(true)
    {
        //消费活动,不断向阻塞队列中取
    }
	return nullptr;
}

int main()
{
    BlockQueue<int>\* bq=new BlockQueue<int>();

    pthread_t p,c;
    //两个线程看到同一个阻塞队列
    pthread\_create(&p,nullptr,productor,bq);
    pthread\_create(&c,nullptr,consumer,bq);

    pthread\_join(p,nullptr);
    pthread\_join(p,nullptr);

    delete bq;

    return 0;
}

阻塞队列大的逻辑框架

#pragma onec

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

using namespace std;

const int maxcapacity=5;
 
template<class T>
class BlockQueue
{
public:
    BlockQueue(const int& _capacity=maxcapacity)
        :\_capacity(capacity)
    {
        
    }

    //生产者放数据
    void push(const T& in)//输入型参数,const &
    {

    }

    //消费者拿数据
    void pop(T\* out)//输出型参数,\* //输入输出型 &
    {

    }

    ~BlockQueue()
    {

    }

private:
    queue<T> _q;
    int _capacity;//不能让阻塞队列无限扩容,所以给一个最大容量表示队列的上限
    pthread_mutex_t _mutex;//阻塞队列是一个共享资源,所以需要一把锁把它保护起来
    //生产者对应的条件变量
    pthread_cond_t _pcond;//队列满了,一定要让生产者在对应的条件变量下休眠
    //消费者对应的条件变量
    pthread_cond_t _ccond;//队列空了,让消费者也在对应条件变量下休眠
};

接下来把代码写完

#pragma onec

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

using namespace std;

const int maxcapacity=5;
 
template<class T>
class BlockQueue
{
public:
    BlockQueue(const int& capacity=maxcapacity)
        :\_capacity(capacity)
    {
        //构造时初始化
        pthread\_mutex\_init(&_mutex,nullptr);
        pthread\_cond\_init(&_pcond,nullptr);
        pthread\_cond\_init(&_ccond,nullptr);
    }

    //生产者放数据
    void push(const T& in)//输入型参数,const &
    {
        //放之前先加锁保护共享资源,在加锁和解锁之间就是安全的临界资源
        pthread\_mutex\_lock(&_mutex);
        //1.判满
        if(is\_full())//bug?
        {
            //因为生产条件不满足,无法生产,此时我们的生产者进行等待
            pthread\_cond\_wait(&_pcond,&_mutex);//\_muext?
        }
        //2.走到这里一定是没有满
        _q.push(in);
        //3.绝对能保证,阻塞队列里面一定有数据
        pthread\_cond\_signal(&_ccond);//唤醒消费者,这里可以有一定策略,比如说满足三分之一在唤醒
        pthread\_mutex\_unlock(&_mutex);

    }

    //消费者拿数据
    void pop(T\* out)//输出型参数,\* //输入输出型 &
    {
        //这里也要加锁,因为要保证访问同一份资源是安全,所以用的是同一把锁
        pthread\_mutex\_lock(&_mutex);
        //1.判空
        if(is\_empty())//bug?
        {
            pthread\_cond\_wait(&_ccond,&_mutex);//\_mutex?
        }
        //2.走到这里我们能保证,一定不为空
        \*out=_q.front();
        _q.pop();
        //3.绝对能保证,阻塞队列里面至少有一个空的位置
        pthread\_cond\_signal(&_pcond);//这里可以有一定策略
        pthread\_mutex\_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        //析构时销毁
        pthread\_mutex\_destroy(&_mutex);
        pthread\_cond\_destroy(&_pcond);
        pthread\_cond\_destroy(&_ccond);
    }

private:
    bool is\_full()
    {
        return _q.size()==_capacity;
    }

    bool is\_empty()
    {
        return _q.empty();
    }

private:
    queue<T> _q;
    int _capacity;//不能让阻塞队列无限扩容,所以给一个最大容量表示队列的上限
    pthread_mutex_t _mutex;//阻塞队列是一个共享资源,所以需要一把锁把它保护起来
    //生产者对应的条件变量
    pthread_cond_t _pcond;//队列满了,一定要让生产者在对应的条件变量下休眠
    //消费者对应的条件变量
    pthread_cond_t _ccond;//队列空了,让消费者也在对应条件变量下休眠
};

#include"BlockQueue.hpp"
#include<ctime>
#include<unistd.h>

void\* productor(void\* args)
{
    BlockQueue<int>\* bq=static\_cast<BlockQueue<int>\*>(args);

    while(true)
    {
        //生产活动
        int data=rand()%10+1;//在这里先用随机数.构建一个数据
        bq->push(data);
        cout<<"生产数据: "<<data<<endl;
        sleep(1);//生产的慢一些
    }
	return nullptr;
}

void\* consumer(void\* args)
{
    BlockQueue<int>\* bq=static\_cast<BlockQueue<int>\*>(args);
    while(true)
    {
        //消费活动
        int data;
        bq->pop(&data);
        cout<<"消费数据: "<<data<<endl;
    }
	return nullptr;
}

int main()
{
    //随机数种子
    srand((unsigned int)time(nullptr));

    BlockQueue<int>\* bq=new BlockQueue<int>();

    pthread_t p,c;
    //两个线程看到同一个阻塞队列
    pthread\_create(&p,nullptr,productor,bq);
    pthread\_create(&c,nullptr,consumer,bq);

    pthread\_join(p,nullptr);
    pthread\_join(p,nullptr);

    delete bq;

    return 0;
}

如何证明这是一份生产者消费者模型呢?
我们先让生产者慢一点生产,生产一个消费一个
在这里插入图片描述
在让消费者慢一点,看到生产一堆,而消费者只能消费一个,不过消费的是历史数据,消费之后生产者才能继续生产

void\* consumer(void\* args)
{
    BlockQueue<int>\* bq=static\_cast<BlockQueue<int>\*>(args);
    while(true)
    {
        //消费活动
        int data;
        bq->pop(&data);
        cout<<"消费数据: "<<data<<endl;
        sleep(1);//消费的慢一些
    }
}

在这里插入图片描述

代码写了测试也都通过了,但是这份代码还有很多细节需要我们雕琢的地方。

以生产为例
在这里插入图片描述
细节一

首先加锁然后最后才是解锁,在判断满的生产条件不满足被挂起,但是挂起的时候可是在临界区中被挂起,如果我挂起期间还持有锁,那其他线程也进不来。
因此pthread_cond_wait这个函数第二个参数,必须是我们正在使用的互斥锁!
在这里插入图片描述
a.该函调用的时候,会以原子性的方式将锁释放,并将自己挂起
b.该函数在被唤醒返回的时候,会自动的重新获取你传入的锁

如果当前醒来锁没有获取成功,你也必须是处于竞争锁的状态,直到获取锁成功了这个函数才会返回。换言之只要这个函数返回了这个锁一定获取成功了。

细节二

在这里插入图片描述

当前判断生产条件不满足就把自己挂起,但是这有个问题pthread_cond_wait这是一个函数,只要是函数就有调用失败的可能。
另外还存在伪唤醒的情况,假设只有一个消费者,十个生产者。只消费了一个但是却唤醒了一批。但是你这里是if判断,都去push肯定是有问题的。
因此充当条件判断的语法必须是while,不能用if

    void push(const T& in)//输入型参数,const &
    {
        //放之前先加锁保护共享资源,在加锁和解锁之间就是安全的临界资源
        pthread\_mutex\_lock(&_mutex);
        //1.判满
        while(is\_full())
        {
            //因为生产条件不满足,无法生产,此时我们的生产者进行等待
            pthread\_cond\_wait(&_pcond,&_mutex);
        }
        //2.走到这里一定是没有满
        _q.push(in);
        //3.绝对能保证,阻塞队列里面一定有数据
        pthread\_cond\_signal(&_ccond);//唤醒消费者,这里可以有一定策略,比如说满足三分之一在唤醒
        pthread\_mutex\_unlock(&_mutex);

    }

    //消费者拿数据
    void pop(T\* out)//输出型参数,\* //输入输出型 &
    {
        //这里也要加锁,因为要保证访问同一份资源是安全,所以用的是同一把锁
        pthread\_mutex\_lock(&_mutex);
        //1.判空
        while(is\_empty())
        {
            pthread\_cond\_wait(&_ccond,&_mutex);
        }
        //2.走到这里我们能保证,一定不为空
        \*out=_q.front();
        _q.pop();
        //3.绝对能保证,阻塞队列里面至少有一个空的位置
        pthread\_cond\_signal(&_pcond);//这里可以有一定策略
        pthread\_mutex\_unlock(&_mutex);
    }

细节三
在这里插入图片描述
pthread_cond_signal这个函数,可以放在临界区内部,也可以放在外部。
也就是说这个唤醒可以放在解锁之前也可以放在解锁之后。但是一般建议放在里面。
因为不关心谁拿到锁,只关心有人生产消费。

下面我们修改一下代码,这样生产消费数据太low了,所以我们写了模板可以放任意内容。我们写一个任务。

//Task.hpp
#pragma once
#include <iostream>
#include <functional>
#include <string>
using namespace std;

class Task
{
    typedef function<int(int, int,char)> func_t;

public:
    Task(){}

    Task(int x, int y, char op, func_t func) : \_x(x), \_y(y), \_op(op), \_callback(func)
    {}

    // 把任务返回去可以看到
    string operator()()
    {
        int result = \_callback(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = %d",_x,_op,_y,result);
        return buffer;
    }

    // 把生产的任务也打印出来
    string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"%d %c %d = ?",_x,_op,_y);
        return buffer;
    }

private:
    int _x;
    int _y;
    char _op;         // 对应+-\*/%操作
    func_t _callback; // 回调函数
};


string oper="+-\*/%";

// 回调函数
int mymath(int x, int y, char op)
{
    int result=0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '\*':
        result = x \* y;
        break;
    case '/':
        {
            if (y == 0)
            {
                cout << "div zero error" << endl;
                result = -1;
            }
            else
            {
                result = x / y;
            }
        }
        break;
    case '%':
        {
            if (y == 0)
            {
                cout << "mod zero error" << endl;
                result = -1;
            }
            else
            {
                result = x % y;
            }
        }
        break;
    default:
        break;
    }
    return result;
}

#include"BlockQueue.hpp"
#include<ctime>
#include<unistd.h>
#include"Task.hpp"

void\* productor(void\* args)
{
    BlockQueue<Task>\* bq=static\_cast<BlockQueue<Task>\*>(args);

    while(true)
    {
        //生产活动
        int x=rand()%10+1;
        int y=rand()%5;
        char op=oper[rand()%oper.size()];
        Task t(x,y,op,mymath);
        bq->push(t);
        cout<<"生产任务: "<<t.toTaskString()<<endl;

        sleep(1);//生产的慢一些
    }

}

void\* consumer(void\* args)
{
    BlockQueue<Task>\* bq=static\_cast<BlockQueue<Task>\*>(args);

    while(true)
    {
        Task t;
        bq->pop(&t);
        cout<<"消费任务: "<<t()<<endl;
    }

}

int main()
{
    //随机数种子
    srand((unsigned int)time(nullptr));
    BlockQueue<Task>\* bq=new BlockQueue<Task>();
    
    pthread_t p,c;
    //两个线程看到同一个阻塞队列
    pthread\_create(&p,nullptr,productor,bq);
    pthread\_create(&c,nullptr,consumer,bq);

    pthread\_join(p,nullptr);
    pthread\_join(p,nullptr);

    delete bq;

    return 0;
}

在这里插入图片描述

现在我还想把需求变一变,我让一个线程来生产派发任务,另一个线程来消费处理任务,再来一个线程记录任务结果,将结果记录在文件中!该怎么办呢?
再来一个阻塞队列!
在这里插入图片描述

#pragma once
#include <iostream>
#include <functional>
#include <string>
using namespace std;

class CallTask
{
    typedef function<int(int, int, char)> func_t;

public:
    CallTask() {}

    CallTask(int x, int y, char op, func_t func) : \_x(x), \_y(y), \_op(op), \_callback(func)
    {
    }

    // 把任务返回去可以看到
    string operator()()
    {
        int result = \_callback(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = %d", _x, _op, _y, result);
        return buffer;
    }

    // 把生产的任务也打印出来
    string toTaskString()
    {
        char buffer[1024];
        snprintf(buffer, sizeof buffer, "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }

private:
    int _x;
    int _y;
    char _op;         // 对应+-\*/%操作
    func_t _callback; // 回调函数
};

string oper = "+-\*/%";

// 回调函数
int mymath(int x, int y, char op)
{
    int result = 0;
    switch (op)
    {
    case '+':
        result = x + y;
        break;
    case '-':
        result = x - y;
        break;
    case '\*':
        result = x \* y;
        break;
    case '/':
    {
        if (y == 0)
        {
            cout << "div zero error" << endl;
            result = -1;
        }
        else
        {
            result = x / y;
        }
    }
    break;
    case '%':
    {
        if (y == 0)
        {
            cout << "mod zero error" << endl;
            result = -1;
        }
        else
        {
            result = x % y;
        }
    }
    break;
    default:
        break;
    }
    return result;
}

class SaveTask
{
    typedef function<void(string)> func_t;
public:
    SaveTask(){}

    SaveTask(string messages,func_t func):\_messages(messages),\_func(func)
    {}

    void operator()()
    {
        \_func(_messages);
    }

private:
    string _messages;
    func_t _func;
};

void Save(string messages)
{
    string target="./log.txt";
    FILE\* fp=fopen(target.c\_str(),"a+");
    if(!fp)
    {
        cout<<"fopen error"<<endl;
        return;
    }
    fprintf(fp,"%s\n",messages.c\_str());
    fclose(fp);
}

#include"BlockQueue.hpp"
#include<ctime>
#include<unistd.h>
#include"Task.hpp"

//C 计算
//S 存储
template<class C,class S>
class BlockQueues
{
public:
    BlockQueue<C>\* c_bq;
    BlockQueue<S>\* s_bq;
};

void\* productor(void\* args)
{
    BlockQueue<CallTask>\* _c_bq=(static\_cast<BlockQueues<CallTask,SaveTask>\*>(args))->c_bq;

    while(true)
    {
        //生产活动
        int x=rand()%10+1;
        int y=rand()%5;
        char op=oper[rand()%oper.size()];
        CallTask t(x,y,op,mymath);
        _c_bq->push(t);
        cout<<"productor thread, 生产计算任务: "<<t.toTaskString()<<endl; 

        sleep(1);//生产的慢一些
    }

}

void\* consumer(void\* args)
{
    BlockQueue<CallTask>\* _c_bq=(static\_cast<BlockQueues<CallTask,SaveTask>\*>(args))->c_bq;
    BlockQueue<SaveTask>\* _s_bq=(static\_cast<BlockQueues<CallTask,SaveTask>\*>(args))->s_bq;


    while(true)
    {
        CallTask t;
        _c_bq->pop(&t);
        cout<< "cal thread, 完成计算任务: "<<t()<<endl;



![](https://img-blog.csdnimg.cn/img_convert/9a8cb5f8c0ec69e6499adead0da6e95b.png)


最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/59742364bb1338737fe2d315a9e2ec54.png)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**



====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 30
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值