C++ 实现多线程的生产者(producer) - 消费者(consumer) 模型

1. 模型介绍:

生产者消费者模型是操作系统中的一种并发编程模型,用于解决生产者和消费者之间的数据共享和同步问题。

在该模型中,生产者负责生成数据,并将数据放入一个有限的缓冲区中,而消费者则从缓冲区中取出数据进行处理。

两者之间通过共享的缓冲区进行通信。

2. 模型实现的要素:

为了实现正确的数据传输和同步,需要维护以下几个要素:

  • 缓冲区:用来存放生产者生成的数据。其大小可能是固定的或动态调整的。
  • 生产者:根据一定规则生成数据,并将数据放入缓冲区中。如果缓冲区已满,则需要等待消费者取出部分数据才能继续生产。
  • 消费者:从缓冲区中取出数据,并进行相应处理。如果缓冲区为空,则需要等待生产者放入新的数据才能继续消费。
  • 同步机制:用于保证生产者和消费者之间的顺序执行和互斥访问。常见的同步机制包括信号量、互斥锁、条件变量等。

在实际应用中,可以采用多线程或多进程来实现生产者消费者模型。多线程方案中,可以使用线程间共享的全局变量作为缓冲区,并利用互斥锁和条件变量来实现线程间的同步。多进程方案中,可以使用进程间共享的内存或文件来作为缓冲区,并利用信号量等机制来实现进程间的同步。

生产者消费者模型能够有效地解耦生产者和消费者之间的耦合关系,提高系统的并发性和吞吐量。然而,需要注意的是,在设计和实现过程中,需要考虑线程安全、死锁、饥饿等问题。同时,根据具体情况选择适当的同步机制和缓冲区大小也是非常重要的。

3. 经典的库和框架使用

生产者消费者模型来实现并发编程和数据处理。以下是其中一些常见的例子:

  • Java中的BlockingQueue:Java标准库提供了BlockingQueue接口,它实现了生产者消费者模型。生产者可以将数据放入队列中,而消费者可以从队列中取出数据进行处理。BlockingQueue提供了阻塞的插入和移除方法,确保当队列已满或为空时,调用线程会被阻塞。
  • Python中的queue模块:Python标准库中的queue模块提供了多种队列实现,包括FIFO队列(Queue)、LIFO队列(LifoQueue)和优先级队列(PriorityQueue)。这些队列都可以用于实现生产者消费者模型,并提供了多线程安全的操作方法。
  • POSIX线程库(pthread):POSIX线程库是一套用于操作系统级线程编程的标准接口。它提供了互斥锁(mutex)、条件变量(condition variable)等同步机制,可以通过这些机制来实现生产者消费者模型。
  • Apache Kafka:Apache Kafka是一个分布式流处理平台,广泛应用于大规模数据处理和消息传递场景。Kafka使用基于生产者消费者模型的消息队列来处理高吞吐量的流式数据。生产者将数据写入Kafka的主题(topic),而消费者从主题中读取和处理数据。
  • RabbitMQ:RabbitMQ是一个开源的消息队列中间件,支持多种消息传递协议。它使用生产者消费者模型来实现可靠的消息传递和异步通信。生产者将消息发送到RabbitMQ的交换机(exchange),而消费者从队列中接收和处理消息。

这些库和框架提供了一种方便而可靠的方式来实现生产者消费者模型,简化了并发编程和数据处理的复杂性。可以根据具体需求选择合适的库来实现并发任务和数据流处理。

4. C++ 代码实现

C++ 复制代码
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory>

class ITask {
public:
  virtual void execute() = 0;
  virtual bool isLast() const = 0;
};

std::mutex mutexLock;
std::condition_variable condVar;

void producer(std::queue<std::shared_ptr<ITask>>& dataQueue)
{
  for (int i = 0; i < 10; ++i)
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Simulate some work

    // Create and enqueue a new task
    std::shared_ptr<ITask> task = std::make_shared<MyTask>(i);

    std::lock_guard<std::mutex> lock(mutexLock);
    dataQueue.push(task);
    std::cout << "Produced: " << i << std::endl;
    condVar.notify_one();
  }
}

void consumer(std::queue<std::shared_ptr<ITask>>& dataQueue)
{
  while (true)
  {
    std::unique_lock<std::mutex> lock(mutexLock);

    if (condVar.wait_for(lock, std::chrono::milliseconds(1000), [&dataQueue] { return !dataQueue.empty(); }))
    {
      std::shared_ptr<ITask> task = dataQueue.front();
      dataQueue.pop();
      lock.unlock();

      // Execute the task
      task->execute();

      if (task->isLast())
        break;
    }
    else
    {
      // Timeout occurred, do something else or exit
      lock.unlock();
      std::cout << "Consumer timed out." << std::endl;
      break;
    }
  }
}

class MyTask : public ITask {
private:
  int data;

public:
  MyTask(int d) : data(d) {}

  void execute() override {
    std::cout << "Consumed: " << data << std::endl;
  }

  bool isLast() const override {
    return data == 9;
  }
};

int main_()
{
  std::queue<std::shared_ptr<ITask>> dataQueue;

  // Start producer and consumer in separate threads
  std::thread producerThread(producer, std::ref(dataQueue));
  std::thread consumerThread(consumer, std::ref(dataQueue));

  producerThread.join();
  consumerThread.join();
}

5. 代码详解:

  • 抽象出 ITask 类,用户可以继承实现不同任务;
  • 线程执行 producer 函数,生产任务放到队列中;
  • 线程执行 consumer 函数,从队列中取任务,执行;
  • 主线程等待任务完全完成;
相关推荐
冷眼看人间恩怨32 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客41 分钟前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin43 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos2 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室3 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0013 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我583 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc3 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很3 小时前
C++ 集合 list 使用
c++
诚丞成4 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++