线程同步与互斥——生产者、消费者模型

1. 引入

生产消费模型,是一种同步关系,是多进程、多线程,同步互斥的一种场景、策略

简称为cp问题,Producer - Consumer

以日常生活的超市为例:

消费者不需要去等待工厂的生产,而可以直接在超市购买;同样地,生产者也不需要等待消费者前来购买,生产出来后,直接存放到超市即可。

超市的存在,带来了明显的优点:

1.1 优势一:缓存

有了超市之后,效率明显地提高,超市就像一个大号的缓存

对于这个缓存,供货商关注的是空间;而消费者关注的是商品数。

因为缓存的存在,本质上,调整了供货商和消费者之间

因为速度不一致,而带来的效率问题

1.2 优势二:支持忙闲不均

同时有了超市的存在,支持忙闲不均

比如过年时,供货商要放假,但是消费者要消费,

没关系,有超市的存在,供货商提前生产好商品,

存储在超市,消费者消费时,去超市消费即可

1.3 优势三:解耦

有了超市的存在,生产和消费的行为,进行了一定程度的解耦

供货商在生产商品时,消费者是不关心的;

消费者在使用商品时,供货商也是不关心的

多线程强调并发,并发就要强调解耦,解耦工作做好,并发度才能更好。

2. 实际应用:多线程

实际的应用下,生产者和消费者的角色,都是由线程来承担的。

生产、消费的过程,本质就是,执行流在做通信

我们关注的重点是,如何安全高效的通信

2.1 共享资源的并发问题

内存空间,作为C、P的共享资源,访问时,存在着并发问题。

三种关系:

2.1.1 生产者 vs 生产者

生产者 和 生产者 之间,要保证互斥关系。

供货商之间,比如康师傅和统一,二者就是竞争关系 ,理想情况是,只有我一家存在。但是不太可能做到。

但至少要保证,一家供货商,在超市供货时,别的厂家不能进行供货,要等待。

二者是纯竞争关系,就不需要保证顺序了。

2.1.2 消费者 vs 消费者

消费者 和 消费者 之间,也要保证互斥关系。

补充:把超市看作100个展柜,那么C1使用1展柜,C2使用2展柜,不就不冲突了吗。
目前就把超市看作整体,不考虑这种情况。

2.1.3 生产者 vs 消费者

生产者 和 消费者 之间,要保证互斥和同步关系。

互斥主要是为了保证安全。

摆放(生产)商品和拿取商品的操作,必须是原子的。

不能存在不确定性,比如消费者是否拿到商品取决于,商品是否被摆在了柜台上,可能已经摆上去了,也可能没有,这就会导致不确定性,要避免这种情况。

保证同步。

同时要保证同步,如果供货时频繁去访问超市,消费者就无法访问了,因为要保证二者的互斥,就会导致消费者饥饿问题。所以生产和消费,要有一定顺序性

比如供货商打电话,进行供货,打完这次之后,就不能再打了。

比如要求5天后再打,从而保证消费者的电话能打进来。反之亦是如此

2.2 总结

我们可以使用321原则,概括生产者、消费者模型。

3:3种关系,C和C、P和P、C和P。
2:2种角色,生产者和消费者。注意,每种角色可能有多个
1:1个交易场所,也就是特定结构的内存空间。

优点:

一、支持忙闲不均

二、生产和消费进行解耦

2.2.1 关于耦合

生产和消费,只会通过交易的中间场所,产生一定的耦合,其他情况下,是互不干扰的。

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

3.1 代码实现

借助C++的queue,模拟阻塞队列的生产消费模型

队列有上限,也可以为空

单生产者、单消费者的代码如下:

cpp 复制代码
//BlockQueue.hpp

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

using namespace std;

template <class T>
class BlockQueue
{
public:
    BlockQueue(int max_size = 5) : _max_size(max_size)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

    void push(const T &data)
    {
        pthread_mutex_lock(&_mutex);
        if (_q.size() == _max_size) // 可以写成循环?实际上,一定是队列有空位置时,才唤醒生产者
        {
            pthread_cond_wait(&_p_cond, &_mutex); // 队列满了之后,就不能进行生产了
        }

        // 1.队列不为满  2.被唤醒并且拿到锁资源
        _q.push(data);

        // 此时队列一定被生产了一个数据,此时可以唤醒消费者
        pthread_cond_signal(&_c_cond);

        pthread_mutex_unlock(&_mutex);
    }

    T pop()
    {
        pthread_mutex_lock(&_mutex);
        if (_q.size() == 0)
        {
            pthread_cond_wait(&_c_cond, &_mutex); // 队列空了之后,就不能进行消费了
        }

        // 1.队列不为空  2.被唤醒并且拿到锁资源
        T data = _q.front();
        _q.pop();

        // 此时队列一定被消费了一个数据,此时可以唤醒生产者
        pthread_cond_signal(&_p_cond);

        pthread_mutex_unlock(&_mutex); // 正确的解锁位置

        return data;
    }

private:
    queue<T> _q; // 所访问的共享资源
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond;
    pthread_cond_t _c_cond;

    int _max_size; // 极值,规定的队列最大容量
};


//main.cpp

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

void *Produce(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

    int data = 1;
    while (1)
    {
        bq->push(data);

        cout << "生产了一个数:" << data << endl;
        data++;
        // sleep(1);
    }
}

void *Consume(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

    while (1)
    {
        sleep(1);
        int data = bq->pop();

        cout << "消费了一个数:" << data << endl;
    }
}

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

    pthread_t p, c;
    pthread_create(&p, nullptr, Produce, bq);
    pthread_create(&c, nullptr, Consume, bq);

    // 主线程进行等待
    pthread_join(p, nullptr);
    pthread_join(c, nullptr);

    delete bq;

    return 0;
}

如图:

3.2 注意事项

3.2.1 提问

链接:豆包的解答

3.3 添加高、低水位线

相关推荐
雾岛听蓝1 小时前
C文件操作与系统IO
linux·c语言·开发语言·经验分享·笔记·算法
盐焗西兰花2 小时前
鸿蒙学习实战之路-Share Kit系列(7/17)-自定义分享面板操作区
linux·学习·harmonyos
zhim002 小时前
【保姆级教程】使用 Docker 部署 PostgreSQL + pgvector(含踩坑指南)
linux·docker
开朗觉觉3 小时前
将json字符串转换为json对象
linux·服务器·python
脱脱克克3 小时前
OpenClaw 腾讯云 + 火山方舟(Volcengine Ark)完整安装与扩展教程
linux·腾讯云·openclaw
cyber_两只龙宝4 小时前
【MySQL】MySQL主从复制架构
linux·运维·数据库·mysql·云原生·架构
Lolo_fi4 小时前
Linux PCI/PCIe子系统
linux
虚拟世界AI4 小时前
Linux运维实战:从部署到高可用全指南
linux·运维
闫记康4 小时前
scp工具
linux·运维·服务器·学习·ssh·github