【Linux后端服务器开发】多线程——生产消费模型

目录

一、生产消费模型

二、BlockQueue阻塞队列模型

BlockQueue.hpp

Task.hpp

mypc.cc

三、RingQueue循环队列模型

POSIX信号量

RingQueue.hpp

Task.hpp

main.cc


一、生产消费模型

生产者与生产者之间关系:互斥(竞争)

消费者与消费者之间关系:互斥(竞争)

生产者和消费者之间关系:互斥(不能同时访问同一个资源)&& 同步(生产与消费可同时进行)

二、BlockQueue阻塞队列模型

生产消费模型的任务存取由于加锁解锁过程是串行执行的,所以从阻塞队列中存入和取出任务并不高效,而高效之处体现在生产任务之前和消费任务之后的多线程并发执行

先加锁、再检测生产或消费条件是否满足、再操作、再解锁

当阻塞队列满的时候,生产者进行阻塞等待,当阻塞队列空的时候,消费者进行阻塞等待

BlockQueue.hpp

#pragma once

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

const int g_maxCap = 5;

template<class T>
class BlockQueue 
{
    public:
    BlockQueue(const int& maxCap = g_maxCap) 
        : _maxCap(maxCap) 
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_pcond, nullptr);
        pthread_cond_init(&_ccond, nullptr);
    }
    void push(const T& in) 
    {
        pthread_mutex_lock(&_mutex);
        while (is_full()) 
        {
            // pthread_cond_wait这个函数的第二个参数,必须是正在使用的互斥锁
            // pthread_cond_wait该函数调用的时候,以原子性的方式,将锁释放,并将自己挂起
            // pthread_cond_wait该函数被唤醒返回的时候,会自动重新获取你传入的锁
            pthread_cond_wait(&_pcond, &_mutex);
        }
        _q.push(in);
        // pthread_cond_signal这个函数可以放在临界区内部,也可以放在外部
        pthread_cond_signal(&_ccond);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T* out) 
    {
        pthread_mutex_lock(&_mutex);
        while (is_empty()) 
        {
            pthread_cond_wait(&_ccond, &_mutex);
        }
        *out = _q.front();
        _q.pop();
        pthread_cond_signal(&_pcond);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue() 
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_pcond);
        pthread_cond_destroy(&_ccond);
    }

    private:
    bool is_empty() 
    {
        return _q.empty();
    }
    bool is_full() 
    {
        return _q.size() == _maxCap;
    }


    private:
    std::queue<T> _q;
    int _maxCap;
    pthread_mutex_t _mutex;
    pthread_cond_t _pcond;      //生产者对应的条件变量
    pthread_cond_t _ccond;      //消费者对应的条件变量
};

Task.hpp

#pragma once

#include <iostream>
#include <functional>
#include <cstdio>
#include <string>

class CalTask 
{
    using func_t = std::function<int(int, int, char)>;

    public:
    CalTask() 
    {}
    CalTask(int x, int y, char op, func_t func) 
        : _x(x), _y(y), _op(op), _callbask(func) 
    {}

    std::string operator()() 
    {
        int result = _callbask(_x, _y, _op);
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "%d %c %d = %d", _x, _op, _y, result);
        return buffer;
    }
    std::string toTaskString() 
    {
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }

    private:
    int _x, _y;
    char _op;
    func_t _callbask;
};

const std::string oper = "+-*/%";

int myMath(int x, int y, int op) 
{
    if (y == 0 && (op == '/' || op == '%')) 
    {
        std::cerr << "div zero error!" << std::endl;
        return -1;
    }
    switch (op) 
    {
        case '+': return x + y;
        case '-': return x - y;
        case '*': return x * y;
        case '/': return x / y;
        case '%': return x % y;
        default:
            std::cerr << "oper erro!" << std::endl;
            return -1;
    }
}

class SaveTask 
{
    typedef std::function<void(const std::string&)> func_t;

    public:
    SaveTask() 
    {}
    SaveTask(const std::string& message, func_t func)
        : _message(message), _func(func) 
    {}

    void operator()() 
    {
        _func(_message);
    }

    private:
    std::string _message;
    func_t _func;
};

void Save(const std::string& message) 
{
    FILE* pf = fopen("./log.txt", "a");
    if (!pf) 
    {
        std::cerr << "fopen error" << std::endl;
        return;
    }
    fputs(message.c_str(), pf);
    fputs("\n", pf);
    fclose(pf);
}

mypc.cc

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

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

void* productor(void* _bqs) 
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(_bqs))->c_bq;
    while (true) 
    {
        // sleep(2);
        int x = rand() % 100 + 1;
        int y = rand() % 10;
        int operCode = rand() % oper.size();
        CalTask t(x, y, oper[operCode], myMath);
        bq->push(t);
        std::cout << "productor thread, 生产计算任务: " << t.toTaskString() << std::endl;
    }
    return nullptr;
}

void* consumer(void* _bqs) 
{
    BlockQueue<CalTask>* bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(_bqs))->c_bq;
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(_bqs))->s_bq;
    while (true) 
    {
        CalTask t;
        bq->pop(&t);
        std::string result = t();
        std::cout << "cal thread, 完成计算任务: " << result << "...done" << std::endl;
        SaveTask save(result, Save);
        save_bq->push(save);
        std::cout << "cal thread, 推送存储任务完成..." << std::endl;
        sleep(1);
    }
    return nullptr;
}

void* Saver(void* _bqs) 
{
    BlockQueue<SaveTask>* save_bq = (static_cast<BlockQueues<CalTask, SaveTask>*>(_bqs))->s_bq;
    while (true) 
    {
        SaveTask t;
        save_bq->pop(&t);
        t();
        std::cout << "save thread, 保存任务完成..." << std::endl;
    }
    return nullptr;
}

int main() 
{
    srand((unsigned long)time(nullptr) ^ getpid());
    BlockQueues<CalTask, SaveTask> bqs;
    bqs.c_bq = new BlockQueue<CalTask>();
    bqs.s_bq = new BlockQueue<SaveTask>();

    pthread_t p[3], c[2], s;
    pthread_create(p, nullptr, productor, &bqs);
    pthread_create(p + 1, nullptr, productor, &bqs);
    pthread_create(p + 2, nullptr, productor, &bqs);
    pthread_create(c, nullptr, consumer, &bqs);
    pthread_create(c + 1, nullptr, productor, &bqs);
    pthread_create(&s, nullptr, Saver, &bqs);


    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);
    pthread_join(p[2], nullptr);
    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(s, nullptr);

    delete bqs.c_bq;
    delete bqs.s_bq;
    return 0;
}

三、RingQueue循环队列模型

POSIX信号量

  • 信号量本质是一个计数器:衡量临界资源中资源数量的计数器
  • 一份公共资源,运行同时访问不同的区域
  • 不同的线程可以并发访问公共资源的不同区域
  • 只要拥有信号量,就在未来一定拥有临界资源的一部分
  • 申请信号量的本质:对临界资源的特定小块资源的预定机制
  • 通过信号量,在线程真正访问临界资源之前,就已经提前知道了临界资源的使用情况

RingQueue.hpp

#pragma once

#include <iostream>
#include <pthread.h>
#include <semaphore.h>
#include <vector>
#include <cassert>

static int g_cap = 5;

template<class T>
class RingQueue
{
public:
    RingQueue(const int& cap = g_cap)
        : _q(cap), _cap(cap)
    {
        int n = sem_init(&_spaceSem, 0, _cap);
        assert(n == 0);
        n = sem_init(&_dataSem, 0, 0);
        assert(n == 0);
        _prdStep = _csmStep = 0;
        pthread_mutex_init(&_pmutex, nullptr);
        pthread_mutex_init(&_cmutex, nullptr);
    }

    // 生产者
    void push(const T& in)
    {
        P(_spaceSem);   // 申请到了空间信号量,表示对空间进行预定
        pthread_mutex_lock(&_pmutex);
        _q[_prdStep++] = in;
        _prdStep %= _cap;
        pthread_mutex_unlock(&_pmutex);
        V(_dataSem);
    }

    // 消费者
    void pop(T* out)
    {
        P(_dataSem);
        pthread_mutex_lock(&_cmutex);
        *out = _q[_csmStep++];
        _csmStep %= _cap;
        pthread_mutex_unlock(&_cmutex);
        V(_spaceSem);
    }

    ~RingQueue()
    {
        sem_destroy(&_spaceSem);
        sem_destroy(&_dataSem);
        pthread_mutex_destroy(&_pmutex);
        pthread_mutex_destroy(&_cmutex);
    }

private:
    void P(sem_t& sem)
    {
        // lock a semaphore
        int n = sem_wait(&sem);
        assert(n == 0);
    }

    void V(sem_t& sem)
    {
        // unlock a semaphore
        int n = sem_post(&sem);
        assert(n == 0);
    }

private:
    std::vector<T> _q;
    int _cap;
    sem_t _spaceSem;    // 生产者:根据空间资源生产
    sem_t _dataSem;     // 消费者:根据数据资源消费
    int _prdStep;       // productor
    int _csmStep;       // consumer
    pthread_mutex_t _pmutex;
    pthread_mutex_t _cmutex;
};

Task.hpp

#pragma once

#include <iostream>
#include <functional>
#include <fstream>

class CalTask
{
    using func_t = std::function<int(int, int, char)>;
public:
    CalTask()
    {}

    CalTask(int x, int y, char op, func_t func)
        : _x(x), _y(y), _op(op), _func(func)
    {}

    std::string to_task_str()
    {
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d %c %d = ?", _x, _op, _y);
        return buffer;
    }

    std::string operator()()
    {
        int res = _func(_x, _y, _op);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d %c %d = %d", _x, _op, _y, res);
        return buffer;
    }

private:
    int _x, _y;
    char _op;
    func_t _func;
};

const std::string oper = "+-*/%";

int my_math(int x, int y, char op)
{
    if (y == 0 && (op == '/' || op == '%'))
    {
        std::cerr << "div zero err" << std::endl;
        return -1;
    }

    switch (op)
    {
    case '+':
        return x + y;
    case '-':
        return x - y;
    case '*':
        return x * y;
    case '/':
        return x / y;
    case '%':
        return x % y;
    default:
        std::cerr << "oper err" << std::endl;
        return -1;
    }
}

class SaveTask
{
public:
    SaveTask()
    {}

    SaveTask(std::string& message)
        : _message(message)
    {}

    void operator()()
    {
        std::ofstream ofs("log.txt", std::ios_base::app);
        ofs << _message << std::endl;
    }

private:
    std::string _message;
};

main.cc

#include "Ring_queue.hpp"
#include "Task.hpp"
#include <iostream>
#include <sys/types.h>
#include <ctime>
#include <unistd.h>

// 生产者、消费者数量
#define PRD_NUM 4
#define CSM_NUM 8

template<class C, class S>
class RingQueues
{
public:
    RingQueue<CalTask>* _crq;
    RingQueue<SaveTask>* _srq;
};

std::string self_name()
{
    char name[128];
    snprintf(name, sizeof(name), "thread[0x%x]", pthread_self());
    return name;
}

void* productor_routine(void* rqs)
{
    RingQueue<CalTask>* crq = (static_cast<RingQueues<CalTask, SaveTask>*>(rqs))->_crq;
    while (true)
    {
        int x = rand() % 100;
        int y = rand() % 10;
        char op = oper[rand() % oper.size()];
        CalTask t (x, y, op, my_math);
        crq->push(t);
        std::cout << self_name() << ", 生产了一个任务: " << t.to_task_str() << std::endl;
        sleep(1);
    }
}

void* consumer_routine(void* rqs)
{
    RingQueue<CalTask>* crq = (static_cast<RingQueues<CalTask, SaveTask>*>(rqs))->_crq;
    RingQueue<SaveTask>* srq = (static_cast<RingQueues<CalTask, SaveTask>*>(rqs))->_srq;
    while (true)
    {
        CalTask t;
        crq->pop(&t);
        std::string res = t();
        std::cout << self_name() << ", 消费了一个任务: " << res << std::endl;
        SaveTask st(res);
        srq->push(st);
        std::cout << self_name() << ", 推送了一个任务..." << std::endl;
    }
    return nullptr;
}

void* saver_routine(void* rqs)
{
    RingQueue<SaveTask>* srq = (static_cast<RingQueues<CalTask, SaveTask>*>(rqs))->_srq;
    while (true)
    {
        SaveTask st;
        srq->pop(&st);
        st();
        std::cout << self_name() << "存储任务完成!" << std::endl;
    }
}

int main()
{
    srand((unsigned int)time(nullptr) ^ getpid() ^ pthread_self());
    RingQueues<CalTask, SaveTask> rqs;
    rqs._crq = new RingQueue<CalTask>();
    rqs._srq = new RingQueue<SaveTask>();

    pthread_t p[PRD_NUM], c[CSM_NUM], s;
    for (int i = 0; i < PRD_NUM; ++i)
        pthread_create(p + i, nullptr, productor_routine, &rqs);
    for (int i = 0; i < CSM_NUM; ++i)
        pthread_create(p + i, nullptr, consumer_routine, &rqs);
    pthread_create(&s, nullptr, saver_routine, &rqs);

    for (int i = 0; i < PRD_NUM; ++i)
        pthread_join(p[i], nullptr);
    for (int i = 0; i < CSM_NUM; ++i)
        pthread_join(c[i], nullptr);
    pthread_join(s, nullptr);

    delete rqs._crq;
    delete rqs._srq;
    return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AllinTome

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

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

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

打赏作者

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

抵扣说明:

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

余额充值