c++ 生产者消费者满等不丢数据、满超时丢数据的c++实现

服务器开发系列之


前言

生产者消费者模型本身主要是利用一个缓冲区来存储数据,从而减弱生产者和消费者之间的直接关系,提高多线程下程序效率。


一、生产者消费者实现流程

环境准备
实现语言:C++.
C++11提供的thread库
互斥锁mutex
条件变量condition_variable
队列queue
原子操作atomic
开发环境是Centos7.5。

实现细节
前提:
构建一个queue来存储生产的数据,queue不满时可以生产,不空时可以消费。

这个队列,采用阻塞队列的实现思路。
a)先实现构造函数,初始化一个unique_lock供condition_variable使用。
对于条件变量,申请两个,分别控制consumer和producer间的同步。
b)入和出队列的细节。
首先加锁。
循环判断一下目前的队列情况,对于各自的特殊情况(队满和队空)进行处理。
唤醒一个线程来处理特殊情况。
等待处理完毕。
处理入和出队列操作。
c)最后释放锁。

生产者与消费者任务的常见问题
1、对于多线程很多细节不是很理解
2、如何判断多线程同步是成功的。如何调试bug。
3、BUG:在多个consumer线程的情况下,会出现有些线程无法退出的情况(因为在wait等待操作)。
在析构函数里
加入stop,并且唤醒因条件变量阻塞的线程。
在pop函数中,
加入对stop的判断,当队列为空并且stop时,退出pop函数。
对consumer的条件变量wait调用加入pred,队列为空或者没有停止时阻塞。
4、之前对条件变量的wait函数理解不够深入,这次学习了一下。
单参数版本,此时传入一个unique_lock类型的变量,并且已经加锁。调用wait之后释放锁,并阻塞等待notify唤醒。唤醒后加锁。要注意的是被唤醒后有可能加锁失败,此时继续阻塞。
双参数版本,此时需要再加入一个Predicate类型的变量,应该是一个返回bool的函数。我一般用lamda表达式代替。返回false阻塞,true解除。要注意这里的意思是即使notify了,如果后面的条件不满足,也不会解除阻塞。
5、对于多consumer的消息同步暂时没做,是在外部程序完成调用的stop。

二、源码实现

BlockQueue.h

#pragma once
#include <mutex>
#include <thread>
#include <queue>
#include <atomic>
#include <memory>
#include <condition_variable> 
#include <iostream>

int test();


template <class T>
class BlockQueue
{
public:
    BlockQueue();
    ~BlockQueue();

    void stop(){
        _stopped.store(true);
        _cv_con.notify_all();
    }

    bool available(){
        return !stopped() || !empty();
    }

    //when full with Unlimited wait without drop data
    void push(const T &data);
    void pop(T &data);
    //when full with with drop data
    void push_full_drop(const T &data);
private:
    bool stopped() {
        return _stopped.load();
    }
    bool empty() {
        return (int)_datas.size() == 0 ? true : false;
    }
    bool full(){
        return (int)_datas.size() == _capacity ? true : false;
    }
private:
    std::mutex _mt;
    std::condition_variable _cv_con;
    std::condition_variable _cv_prod;
    std::queue<T>   _datas;    
    const int _capacity;
    std::atomic<bool> _stopped;
    std::atomic<uint64_t> _dropdataNUm;
};

BlockQueue.cpp


#include "producer_consume.h"


#define CAPACITY 50


template <class T>
BlockQueue<T>::BlockQueue() :_capacity(CAPACITY), _stopped(false),_dropdataNUm(0)
{
}

template <class T>
BlockQueue<T>::~BlockQueue()
{
    stop();
    _cv_con.notify_all();
    _cv_prod.notify_all();
}

template <class T>
void BlockQueue<T>::push(const T &data) 
{
    std::unique_lock<std::mutex> _lck(_mt);
    while (full()) {
        _cv_con.notify_one();
        _cv_prod.wait(_lck);
    }

    _datas.push(data);
    _cv_con.notify_one();
}

template <class T>
void BlockQueue<T>::push_full_drop(const T &data) 
{
    std::unique_lock<std::mutex> _lck(_mt);
    while(full()) {
        _cv_con.notify_one();
        if(_cv_prod.wait_for(_lck,std::chrono::milliseconds(1000)) == std::cv_status::timeout){
            _dropdataNUm++;
            if(_dropdataNUm >= UINT64_MAX){
                _dropdataNUm = 0;
            }
        }    
        return;
    }

    _datas.push(data);
    _cv_con.notify_one();    
}

template <class T>
void BlockQueue<T>::pop(T &data) 
{
    std::unique_lock<std::mutex> _lck(_mt);
    while (empty()){
        if (this->stopped()) 
            return;
            
        _cv_prod.notify_one();
        // cout << "Queue is empty, notify one producer...\n";
        _cv_con.wait(_lck, [this]() { return this->stopped() || !this->empty(); });    //返回false阻塞,true解除
    }
    data = _datas.front();
    _datas.pop();
    _cv_prod.notify_one();
}

总结

本篇文章主要实现了一种满等不丢数据、满超时等后丢数据的生产者与消费者任务队列。可以根据自己的需求用于不同的场景。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c+猿辅导

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值