多线程条件变量(阻塞队列)下的生产者-消费者模型详解

目录

一、模型的基本概念

二、模型的核心问题

三、阻塞队列的实现

(一)数据结构

(二)构造函数

(三)生产者操作

(四)消费者操作

(五)析构函数

四、信号量和条件变量的使用

(一)条件变量

(二)互斥锁

(三)低水位和高水位

五、生产者和消费者的实现

(一)生产者线程

(二)消费者线程

(三)主函数

六、阻塞队列实现细节

(一) 互斥锁的使用

(二)条件变量的使用

(三)低水位和高水位

(四)生产者操作

(五)消费者操作

(六)析构函数

七、任务类实现细节

(一)构造函数

(二)run 方法

(三)GetTask 和 GetResult 方法

八、主函数的实现细节

7. 总结


一、模型的基本概念

生产者消费者模型是一种经典的多线程同步问题模型,用于解决线程之间数据共享与同步的问题。它包含三个核心组成部分:

  1. 生产者(Producer)

    • 定义:生产者是负责生成数据的线程或进程。

    • 作用:生产者会不断地生成数据,并将这些数据放入一个共享缓冲区中。

    • 限制:如果共享缓冲区已经满了,生产者无法再添加数据,必须等待缓冲区有空闲位置。

  2. 消费者(Consumer)

    • 定义:消费者是负责从共享缓冲区中取出数据并进行处理的线程或进程。

    • 作用:消费者会不断地从共享缓冲区中取出数据,并对这些数据进行处理。

    • 限制:如果共享缓冲区为空,消费者无法取出数据,必须等待缓冲区中有数据可供消费。

  3. 共享缓冲区(Buffer)

    • 定义:共享缓冲区是一个有限容量的队列,用于存储生产者生成的数据。

    • 作用:共享缓冲区是生产者和消费者之间共享的存储空间,用于数据的传递。

    • 限制:共享缓冲区的容量是有限的,不能无限扩展。

总结一句话:
321原则:3种关系(生产者vs生产者,消费者vs消费者,生产者vs消费者),2种角色(生产者,消费者),1个场所(特定结构的内存空间)

二、模型的核心问题

生产者和消费者在运行过程中会遇到以下核心问题:

  1. 生产者不能向空缓冲区添加数据

    • 问题描述:如果共享缓冲区已经满了,生产者无法再添加数据。

    • 解决方案:生产者需要等待,直到缓冲区有空闲位置。这通常通过同步机制(如信号量或条件变量)来实现。

  2. 消费者不能从空缓冲区取数据

    • 问题描述:如果共享缓冲区为空,消费者无法取出数据。

    • 解决方案:消费者需要等待,直到缓冲区中有数据可供消费。这同样通过同步机制来实现。

  3. 多线程同步问题

    • 问题描述:生产者和消费者是并发运行的线程,需要确保对共享缓冲区的访问是线程安全的。

    • 解决方案:使用互斥锁(Mutex)或其他同步机制来保护共享缓冲区的访问,避免数据竞争和数据不一致的问题。

三、阻塞队列的实现

(一)数据结构

阻塞队列使用 std::queue 作为底层数据结构,通过互斥锁和条件变量实现线程同步。

std::queue<T> _q; // 内部使用 STL 的 queue 实现
int _maxsize; // 队列的最大容量
pthread_mutex_t _mutex; // 互斥锁,用于保护临界区
pthread_cond_t c_cond; // 消费者条件变量
pthread_cond_t p_cond; // 生产者条件变量
int low_water; // 低水位标记
int high_water; // 高水位标记

(二)构造函数

构造函数初始化队列的最大容量、互斥锁和条件变量,并设置低水位和高水位。

BlockQueue(int maxcap = defaultNum)
    : _maxsize(maxcap)
{
    pthread_mutex_init(&_mutex, nullptr); // 初始化互斥锁
    pthread_cond_init(&c_cond, nullptr); // 初始化消费者条件变量
    pthread_cond_init(&p_cond, nullptr); // 初始化生产者条件变量
    low_water = _maxsize / 3; // 设置低水位
    high_water = _maxsize * 2 / 3; // 设置高水位
}

(三)生产者操作

生产者通过 push 方法将数据放入队列。push 方法的实现步骤如下:

  1. 加锁,确保线程安全。

  2. 检查队列是否已满。如果队列已满,生产者线程将阻塞,等待条件变量 p_cond 的通知。

  3. 将数据放入队列。

  4. 如果队列的大小超过了高水位,通知消费者线程。

  5. 解锁,释放互斥锁。

void push(const T &in)
{
    pthread_mutex_lock(&_mutex); // 加锁
    while (_q.size() == _maxsize) // 如果队列已满
    {
        pthread_cond_wait(&p_cond, &_mutex); // 生产者等待
    }
    _q.push(in); // 将数据加入队列
    if (_q.size() > high_water) // 如果队列高于高水位
        pthread_cond_signal(&c_cond); // 通知消费者
    pthread_mutex_unlock(&_mutex); // 解锁
}

关键点解释

  1. 加锁

    • 使用 pthread_mutex_lock 锁定队列,确保线程安全。互斥锁的作用是防止多个线程同时修改队列,避免数据竞争。

  2. 检查队列是否已满

    • 如果队列已满(_q.size() == _maxsize),生产者线程调用 pthread_cond_wait,进入等待状态。pthread_cond_wait 会自动释放锁,并将线程置于等待队列中,直到被其他线程唤醒。

  3. 入队操作

    • 当队列有空间时,生产者线程被唤醒,将数据放入队列。

  4. 通知消费者

    • 如果队列的大小超过了高水位(_q.size() > high_water),调用 pthread_cond_signal 通知消费者。pthread_cond_signal 会唤醒一个等待在消费者条件变量上的线程。

  5. 解锁

    • 完成操作后,释放锁,允许其他线程访问队列。

(四)消费者操作

消费者通过 pop 方法从队列中取出数据。pop 方法的实现步骤如下:

  1. 加锁,确保线程安全。

  2. 检查队列是否为空。如果队列为空,消费者线程将阻塞,等待条件变量 c_cond 的通知。

  3. 从队列中取出数据。

  4. 如果队列的大小低于低水位,通知生产者线程。

  5. 解锁,释放互斥锁。

  6. 返回取出的数据。

T pop()
{
    pthread_mutex_lock(&_mutex); // 加锁
    while (_q.size() == 0) // 如果队列为空
    {
        pthread_cond_wait(&c_cond, &_mutex); // 消费者等待
    }
    T out = _q.front(); // 获取队列头部数据
    _q.pop(); // 移除队列头部数据
    if (_q.size() < low_water) // 如果队列低于低水位
        pthread_cond_signal(&p_cond); // 通知生产者
    pthread_mutex_unlock(&_mutex); // 解锁
    return out; // 返回数据
}

关键点解释

  1. 加锁

    • 使用 pthread_mutex_lock 锁定队列,确保线程安全。

  2. 检查队列是否为空

    • 如果队列为空(_q.size() == 0),消费者线程调用 pthread_cond_wait,进入等待状态。pthread_cond_wait 会自动释放锁,并将线程置于等待队列中,直到被其他线程唤醒。

  3. 出队操作

    • 当队列有数据时,消费者线程被唤醒,从队列中取出数据。

  4. 通知生产者

    • 如果队列的大小低于低水位(_q.size() < low_water),调用 pthread_cond_signal 通知生产者。pthread_cond_signal 会唤醒一个等待在生产者条件变量上的线程。

  5. 解锁

    • 完成操作后,释放锁,允许其他线程访问队列。

  6. 返回数据

    • 返回取出的数据。

(五)析构函数

在析构函数中,销毁互斥锁和条件变量,释放资源。

~BlockQueue()
{
    pthread_mutex_destroy(&_mutex); // 销毁互斥锁
    pthread_cond_destroy(&c_cond); // 销毁消费者条件变量
    pthread_cond_destroy(&p_cond); // 销毁生产者条件变量
}

四、信号量和条件变量的使用

(一)条件变量

条件变量是一种同步原语,用于线程间的同步。条件变量维护一个等待队列,线程可以在条件不满足时进入等待状态,直到被其他线程唤醒。

  • pthread_cond_wait:等待条件变量。线程在等待时会自动释放锁,并进入等待队列。当线程被唤醒时,它会重新获取锁。

  • pthread_cond_signal:唤醒一个等待在条件变量上的线程。

在阻塞队列中,我们使用两个条件变量:

  • c_cond:消费者条件变量,用于在队列为空时阻塞消费者线程。

  • p_cond:生产者条件变量,用于在队列满时阻塞生产者线程。

(二)互斥锁

互斥锁用于保护对共享资源的访问,确保同一时间只有一个线程可以修改共享资源。在阻塞队列中,我们使用互斥锁 _mutex 保护队列 _q 的访问。

  • pthread_mutex_lock:加锁,确保线程安全。

  • pthread_mutex_unlock:解锁,释放互斥锁。

(三)低水位和高水位

低水位和高水位用于动态调整队列的状态,优化性能:

  • 低水位(low_water:当队列的大小低于低水位时,通知生产者线程。

  • 高水位(high_water:当队列的大小超过高水位时,通知消费者线程。

low_water = _maxsize / 3; // 设置低水位
high_water = _maxsize * 2 / 3; // 设置高水位

五、生产者和消费者的实现

(一)生产者线程

生产者线程负责生成任务,并将任务放入阻塞队列中。生产者线程的实现步骤如下:

  1. 随机生成两个操作数和一个操作符,创建一个 Task 对象。

  2. 调用 push 方法将任务放入队列。

  3. 打印生成的任务信息。

void* Productor(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
    BlockQueue<Task>* bq = td->bq;
    std::string name = td->threadname;

    while (true)
    {
        int data1 = rand() % 10 + 1;
        int data2 = rand() % 10;
        char op = opers[rand() % 5];
        Task t(data1, data2, op);

        bq->push(t);
        std::cout << "生产任务的id是: " << name << " 生产了一个任务: " << t.GetTask() << std::endl;
    }
}

(二)消费者线程

消费者线程负责从阻塞队列中取出任务,并执行任务。消费者线程的实现步骤如下:

  1. 调用 pop 方法从队列中取出任务。

  2. 执行任务。

  3. 打印任务的执行结果。

void* Consumer(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
    BlockQueue<Task>* bq = td->bq;
    std::string name = td->threadname;

    while (true)
    {
        Task t;
        bq->pop(&t);
        t.run();
        std::cout << "消费任务的id是: " << name << " 任务的结果: " << t.GetResult() << std::endl;
        sleep(1);
    }
}

(三)主函数

主函数负责初始化阻塞队列,创建生产者和消费者线程,并启动线程。主函数的实现步骤如下:

  1. 初始化随机数种子。

  2. 创建阻塞队列对象。

  3. 创建消费者线程和生产者线程。

  4. 等待线程结束(理论上不会结束)。

  5. 释放资源。

int main()
{
    srand(time(nullptr)); // 初始化随机数种子

    BlockQueue<Task>* bq = new BlockQueue<Task>();

    pthread_t c[5], p[3];

    for (int i = 0; i < 5; i++)
    {
        ThreadData* td = new ThreadData();
        td->bq = bq;
        td->threadname = "Consumer-" + std::to_string(i);
        pthread_create(c + i, nullptr, Consumer, td);
    }

    for (int i = 0; i < 3; i++)
    {
        ThreadData* td = new ThreadData();
        td->bq = bq;
        td->threadname = "Productor-" + std::to_string(i);
        pthread_create(p + i, nullptr, Productor, td);
    }

    for (int i = 0; i < 5; i++)
    {
        pthread_join(c[i], nullptr);
    }

    for (int i = 0; i < 3; i++)
    {
        pthread_join(p[i], nullptr);
    }

    delete bq;
    return 0;
}

六、阻塞队列实现细节

阻塞队列类完整代码:

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

// 定义默认队列大小
template <class T>
class BlockQueue
{
    static const int defaultNum = 20; // 默认队列大小

private:
    std::queue<T> _q; // 内部使用 STL 的 queue 实现
    int _maxsize; // 队列的最大容量
    pthread_mutex_t _mutex; // 互斥锁,用于保护临界区
    pthread_cond_t c_cond; // 消费者条件变量
    pthread_cond_t p_cond; // 生产者条件变量
    int low_water; // 低水位标记
    int high_water; // 高水位标记

public:
    // 构造函数
    BlockQueue(int maxcap = defaultNum)
        : _maxsize(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&c_cond, nullptr);
        pthread_cond_init(&p_cond, nullptr);
        low_water = _maxsize / 3;
        high_water = _maxsize * 2 / 3;
    }

    // 生产者调用的 push 方法
    void push(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while (_q.size() == _maxsize) // 判断也是访问临界资源,因此必须要有锁;因为存在伪唤醒的状况,因此要使用while而不是if
        {
            pthread_cond_wait(&p_cond, &_mutex); // 生产者等待,直到队列有空间
            // 注意:pthread_cond_wait 会自动释放锁,并在被唤醒时重新加锁
        }
        _q.push(in);
        if (_q.size() > high_water) // 如果队列大小超过高水位
            pthread_cond_signal(&c_cond);
        pthread_mutex_unlock(&_mutex);
    }

    // 消费者调用的 pop 方法
    T pop()
    {
        pthread_mutex_lock(&_mutex);
        while (_q.size() == 0) // 判断也是访问临界资源,因此必须要有锁;因为存在伪唤醒的状况,因此要使用while而不是if
        {
            pthread_cond_wait(&c_cond, &_mutex); // 消费者等待,直到队列有数据
            // 注意:pthread_cond_wait 会自动释放锁,并在被唤醒时重新加锁
        }
        T out = _q.front();
        _q.pop();
        if (_q.size() < low_water)
            pthread_cond_signal(&p_cond);
        pthread_mutex_unlock(&_mutex);
        return out;
    }

    // 析构函数
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex); // 销毁互斥锁
        pthread_cond_destroy(&c_cond); // 销毁消费者条件变量
        pthread_cond_destroy(&p_cond); // 销毁生产者条件变量
    }
};

(一) 互斥锁的使用

互斥锁用于保护对共享资源的访问,确保同一时间只有一个线程可以修改共享资源。在阻塞队列中,我们使用互斥锁 _mutex 保护队列 _q 的访问。

pthread_mutex_t _mutex; // 互斥锁,用于保护临界区

加锁和解锁

  • 加锁

    • 使用 pthread_mutex_lock 锁定队列,确保线程安全。

    • 互斥锁的作用是防止多个线程同时修改队列,避免数据竞争。

    pthread_mutex_lock(&_mutex);
  • 解锁

    • 完成操作后,释放锁,允许其他线程访问队列。

    pthread_mutex_unlock(&_mutex);

(二)条件变量的使用

条件变量用于线程间的同步,允许线程在条件不满足时进入等待状态,直到被其他线程唤醒。

pthread_cond_t c_cond; // 消费者条件变量
pthread_cond_t p_cond; // 生产者条件变量

等待和通知

  • 等待条件变量

    • 使用 pthread_cond_wait 等待条件变量。线程在等待时会自动释放锁,并进入等待队列。当线程被唤醒时,它会重新获取锁。

    cpp

    复制

    pthread_cond_wait(&p_cond, &_mutex);
  • 通知线程

    • 使用 pthread_cond_signal 唤醒一个等待在条件变量上的线程。

    cpp

    复制

    pthread_cond_signal(&c_cond);

(三)低水位和高水位

低水位和高水位用于动态调整队列的状态,优化性能:

  • 低水位(low_water:当队列的大小低于低水位时,通知生产者线程。

  • 高水位(high_water:当队列的大小超过高水位时,通知消费者线程。

low_water = _maxsize / 3; // 设置低水位
high_water = _maxsize * 2 / 3; // 设置高水位

(四)生产者操作

生产者通过 push 方法将数据放入队列。push 方法的实现步骤如下:

  1. 加锁,确保线程安全。

  2. 检查队列是否已满。如果队列已满,生产者线程将阻塞,等待条件变量 p_cond 的通知。

  3. 将数据放入队列。

  4. 如果队列的大小超过了高水位,通知消费者线程。

  5. 解锁,释放互斥锁。

void push(const T &in)
{
    pthread_mutex_lock(&_mutex); // 加锁
    while (_q.size() == _maxsize) // 如果队列已满
    {
        pthread_cond_wait(&p_cond, &_mutex); // 生产者等待
    }
    _q.push(in); // 将数据加入队列
    if (_q.size() > high_water) // 如果队列高于高水位
        pthread_cond_signal(&c_cond); // 通知消费者
    pthread_mutex_unlock(&_mutex); // 解锁
}

关键点解释

  1. 加锁

    • 使用 pthread_mutex_lock 锁定队列,确保线程安全。

  2. 检查队列是否已满

    • 如果队列已满(_q.size() == _maxsize),生产者线程调用 pthread_cond_wait,进入等待状态。pthread_cond_wait 会自动释放锁,并将线程置于等待队列中,直到被其他线程唤醒。

  3. 入队操作

    • 当队列有空间时,生产者线程被唤醒,将数据放入队列。

  4. 通知消费者

    • 如果队列的大小超过了高水位(_q.size() > high_water),调用 pthread_cond_signal 通知消费者。pthread_cond_signal 会唤醒一个等待在消费者条件变量上的线程。

  5. 解锁

    • 完成操作后,释放锁,允许其他线程访问队列。

(五)消费者操作

消费者通过 pop 方法从队列中取出数据。pop 方法的实现步骤如下:

  1. 加锁,确保线程安全。

  2. 检查队列是否为空。如果队列为空,消费者线程将阻塞,等待条件变量 c_cond 的通知。

  3. 从队列中取出数据。

  4. 如果队列的大小低于低水位,通知生产者线程。

  5. 解锁,释放互斥锁。

  6. 返回取出的数据。

T pop()
{
    pthread_mutex_lock(&_mutex); // 加锁
    while (_q.size() == 0) // 如果队列为空
    {
        pthread_cond_wait(&c_cond, &_mutex); // 消费者等待
    }
    T out = _q.front(); // 获取队列头部数据
    _q.pop(); // 移除队列头部数据
    if (_q.size() < low_water) // 如果队列低于低水位
        pthread_cond_signal(&p_cond); // 通知生产者
    pthread_mutex_unlock(&_mutex); // 解锁
    return out; // 返回数据
}

关键点解释

  1. 加锁

    • 使用 pthread_mutex_lock 锁定队列,确保线程安全。

  2. 检查队列是否为空

    • 如果队列为空(_q.size() == 0),消费者线程调用 pthread_cond_wait,进入等待状态。pthread_cond_wait 会自动释放锁,并将线程置于等待队列中,直到被其他线程唤醒。

  3. 出队操作

    • 当队列有数据时,消费者线程被唤醒,从队列中取出数据。

  4. 通知生产者

    • 如果队列的大小低于低水位(_q.size() < low_water),调用 pthread_cond_signal 通知生产者。pthread_cond_signal 会唤醒一个等待在生产者条件变量上的线程。

  5. 解锁

    • 完成操作后,释放锁,允许其他线程访问队列。

  6. 返回数据

    • 返回取出的数据。

(六)析构函数

在析构函数中,销毁互斥锁和条件变量,释放资源。

~BlockQueue()
{
    pthread_mutex_destroy(&_mutex); // 销毁互斥锁
    pthread_cond_destroy(&c_cond); // 销毁消费者条件变量
    pthread_cond_destroy(&p_cond); // 销毁生产者条件变量
}

七、任务类实现细节

任务类 Task 用于表示生产者生成的任务。每个任务包含两个操作数、一个操作符和计算结果。

任务类完整代码:

​
#pragma once
#include <iostream>
#include <string>

std::string opers = "+-*/%"; // 定义全局变量,包含所有可能的操作符

class Task
{
private:
    int _data1;       // 第一个操作数
    int _data2;       // 第二个操作数
    int _result;      // 计算结果
    int _exitcode;    // 退出码,用于表示任务执行的状态
    char _operator;   // 操作符

public:
    Task() // 默认构造函数
    {}

    // 构造函数,初始化任务对象
    Task(int x, int y, char op)
        : _data1(x), _data2(y), _result(0), _operator(op), _exitcode(0)
    {
    }

    // 执行任务的逻辑
    void run()
    {
        switch (_operator) // 根据操作符执行不同的计算逻辑
        {
        case '+': // 加法
            _result = _data1 + _data2;
            break;
        case '-': // 减法
            _result = _data1 - _data2;
            break;
        case '*': // 乘法
            _result = _data1 * _data2;
            break;
        case '/': // 除法
            {
                if (_data2 == 0) // 检查除数是否为0
                    _exitcode = 1; // 设置退出码为1,表示除数为0的错误
                else
                    _result = _data1 / _data2;
            }
            break;
        case '%': // 取模
            {
                if (_data2 == 0) // 检查除数是否为0
                    _exitcode = 1; // 设置退出码为1,表示除数为0的错误
                else
                    _result = _data1 % _data2;
            }
            break;
        default: // 如果操作符无效
            _exitcode = 3; // 设置退出码为3,表示无效操作符
            break;
        }       
    }

    // 重载 () 运算符,方便直接调用任务对象
    void operator()()
    {
        run(); // 调用 run 方法执行任务
    }

    // 获取任务的执行结果,格式化为字符串
    std::string GetResult()
    {
        return std::to_string(_data1) + _operator + std::to_string(_data2) + "=(" + std::to_string(_result) + ") [exit code: " + std::to_string(_exitcode) + "]";
    }

    // 获取任务的描述,格式化为字符串
    std::string GetTask()
    {
        return std::to_string(_data1) + _operator + std::to_string(_data2) + "=?";
    }

    ~Task() // 析构函数
    {
    }
};

​

(一)构造函数

任务类的构造函数初始化任务的两个操作数 _data1_data2,以及操作符 _operator

Task(int x, int y, char op)
    : _data1(x), _data2(y), _result(0), _operator(op), _exitcode(0)
{
}

(二)run方法

run 方法根据操作符执行相应的计算,并将结果存储在 _result 中。如果操作符为除法或取模且第二个操作数为零,设置错误码 _exitcode

void run()
{
    switch (_operator)
    {
    case '+':
        _result = _data1 + _data2;
        break;
    case '-':
        _result = _data1 - _data2;
        break;
    case '*':
        _result = _data1 * _data2;
        break;
    case '/':
        if (_data2 == 0)
            _exitcode = 1; // 除数为零,设置错误码
        else
            _result = _data1 / _data2;
        break;
    case '%':
        if (_data2 == 0)
            _exitcode = 1; // 除数为零,设置错误码
        else
            _result = _data1 % _data2;
        break;
    default:
        _exitcode = 3; // 未知操作符,设置错误码
        break;
    }
}

(三)GetTask 和 GetResult 方法

GetTask 方法返回任务的字符串表示形式,例如 "3+4=?"GetResult 方法返回任务的执行结果,例如 "3+4=(7) [exit code: 0]"

std::string GetTask()
{
    return std::to_string(_data1) + _operator + std::to_string(_data2) + "=?";
}

std::string GetResult()
{
    return std::to_string(_data1) + _operator + std::to_string(_data2) + "=(" + std::to_string(_result) + ") [exit code: " + std::to_string(_exitcode) + "]";
}

八、主函数的实现细节

主函数完整代码:

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

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

void* Consumer(void* args)
{
    BlockQueue<Task>* bq=static_cast<BlockQueue<Task>*>(args);
    while(true)
    {
        // 消费
        Task t=bq->pop();
        // t.run();
        t();
        std::cout<<"运算结果是:"<<t.GetResult()<<" pthread id is: "<<pthread_self()<<std::endl;
        sleep(1);
    }
}

void* Productor(void* args)
{
    BlockQueue<Task>*bq=static_cast<BlockQueue<Task>*>(args);
    while(true)
    {
        // 生产
        int data1=rand()%10;
        int data2=rand()%10;
        char op=oper[rand()%5];
        Task t(data1,data2,op);
        bq->push(t);
        std::cout<<"生产了一个数据是:"<<t.GetTask()<<" pthread id is: "<<pthread_self()<<std::endl;
        
    }
}

int main()
{
    srand(time(nullptr));
    BlockQueue<Task>* bq=new BlockQueue<Task>();
    pthread_t c[3],p[5];
    for(int i=0;i<3;i++)
    {
        pthread_create(c+i,nullptr,Consumer,bq);
    }
    for(int i=0;i<5;i++)
    {
        pthread_create(p+i,nullptr,Productor,bq);
    }
    for(int i=0;i<3;i++)
    {
        pthread_join(c[i],nullptr);
    }
    for(int i=0;i<5;i++)
    {
        pthread_join(p[i],nullptr);
    }
    delete bq;
    return 0;
}

主函数负责初始化阻塞队列,创建生产者和消费者线程,并启动线程。主函数的实现步骤如下:

  1. 初始化随机数种子
  2. 创建阻塞队列对象
  3. 创建消费者线程
  4. 创建生产者线程
  5. 等待线程结束
  6. 释放资源

7. 总结

通过上述代码,我们实现了一个基于条件变量的阻塞队列,适用于生产者消费者模型。关键点包括:

  • 线程安全:通过互斥锁保护队列操作,确保线程安全。

  • 条件变量:用于同步生产者和消费者的行为,确保队列操作的线程安全。

  • 低水位和高水位:用于动态调整队列的状态,优化性能。

  • 生产者和消费者线程:通过阻塞队列实现线程间的通信和同步。

这种模型适用于多线程环境中的任务调度和数据处理,通过调整队列大小和线程数量,可以优化性能以满足不同需求。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值