C++多线程:使用std::condition_variable实现生产者-消费者模型示例

文章提供了一个使用C++和std::condition_variable实现的生产者-消费者模型的示例代码。通过std::atomic和std::queue管理共享资源,生产者线程生产数据并放入队列,消费者线程从队列中消费数据。示例中还包括了停止生产消费的机制。

C++多线程:使用std::condition_variable实现生产者-消费者模型示例

贺志国

示例代码如下:

#include <atomic>
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <queue>

using namespace std::literals::chrono_literals;

class ProducerConsumer {
 public:
  ProducerConsumer() = default;
  ~ProducerConsumer() = default;

 public:
  void Stop() {
    std::cout << "is_stop_.is_lock_free() = " << is_stop_.is_lock_free()
              << '\n';
    is_stop_.store(true);
    task_cond_.notify_one();
  }

  void Produce(size_t items) {
    for (size_t i = 0; i < items; ++i) {
      std::this_thread::sleep_for(100ms);
      {
        std::lock_guard<std::mutex> lk(task_mutex_);
        task_queue_.emplace(i);
      }
      task_cond_.notify_one();
    }

    // is_stop_.store(true);
    // task_cond_.notify_one();
  }

  void Consume() {
    std::unique_lock<std::mutex> lock(task_mutex_);
    while (!is_stop_.load()) {
      task_cond_.wait(
          lock, [this] { return !task_queue_.empty() || is_stop_.load(); });

      while (!task_queue_.empty()) {
        std::cout << "Got " << task_queue_.front() << " from queue.\n";
        task_queue_.pop();
      }
    }
  }

 private:
  std::atomic<bool> is_stop_{false};
  std::queue<size_t> task_queue_;
  std::mutex task_mutex_;
  std::condition_variable task_cond_;
};

int main() {
  ProducerConsumer producer_consumer;

  auto producer = std::async(std::launch::async, &ProducerConsumer::Produce,
                             &producer_consumer, 10);
  auto consumer = std::async(std::launch::async, &ProducerConsumer::Consume,
                             &producer_consumer);
  producer.wait();
  // `consumer.wait()` blocks the current thread until the thread `consumer`
  // finishes its execution. The stop instruction should be generated before
  // `consumer.wait()`
  producer_consumer.Stop();
  consumer.wait();

  std::cout << "Finished!\n";

  return 0;
}

另一种实现方式的代码如下,大家可根据需要挑选具体的实现方式:

#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <queue>
#include <thread>
#include <tuple>

using namespace std::literals::chrono_literals;

class ProducerConsumer {
 public:
  ProducerConsumer() = default;
  ~ProducerConsumer() = default;

 public:
  void Stop() {
    std::cout << "is_stop_.is_lock_free() = " << is_stop_.is_lock_free()
              << '\n';
    is_stop_.store(true);
    task_cond_.notify_one();
  }

  void Produce(size_t items) {
    for (size_t i = 0; i < items; ++i) {
      std::this_thread::sleep_for(100ms);

      {
        std::lock_guard<std::mutex> lk(task_mutex_);
        task_queue_.emplace(i);
      }
      task_cond_.notify_one();
    }
  }

  void Consume() {
    while (!is_stop_.load()) {
      std::unique_lock<std::mutex> lock(task_mutex_);
      task_cond_.wait(
          lock, [this] { return !task_queue_.empty() || is_stop_.load(); });

      while (!task_queue_.empty()) {
        std::cout << "Got " << task_queue_.front() << " from queue.\n";
        task_queue_.pop();
      }
    }
  }

 private:
  std::atomic<bool> is_stop_{false};
  std::queue<size_t> task_queue_;
  std::mutex task_mutex_;
  std::condition_variable task_cond_;
};

int main() {
  ProducerConsumer producer_consumer;
  std::thread t1(&ProducerConsumer::Produce, &producer_consumer, 10);
  std::thread t2(&ProducerConsumer::Consume, &producer_consumer);
  t1.join();
  // `t2.join()` blocks the current thread until the thread `t2` finishes its
  // execution. The stop instruction should be generated before `t2.join()`
  producer_consumer.Stop();
  t2.join();

  std::cout << "Finished!\n";

  return 0;
}

CMake构建文件如下:

cmake_minimum_required(VERSION 3.0.0)
project(producer_consumer VERSION 0.1.0)
set(CMAKE_CXX_STANDARD 14)

add_executable(${PROJECT_NAME} ${PROJECT_NAME}.cpp)

find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})

include(CTest)
enable_testing()
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

运行结果如下:

 ./producer_consumer 
Got 0 from queue.
Got 1 from queue.
Got 2 from queue.
Got 3 from queue.
Got 4 from queue.
Got 5 from queue.
Got 6 from queue.
Got 7 from queue.
Got 8 from queue.
Got 9 from queue.
is_stop_.is_lock_free() = 1
Finished!
<think>我们被要求使用std::condition_variable实现多个生产者和多个消费者模型(多生产者-消费者模型)。根据引用[3]和[4],我们可以构建一个这样的模型。在这个模型中,多个生产者线程向一个共享的队列中放入数据,多个消费者线程从队列中取出数据。我们需要使用互斥锁(std::mutex)来保护队列,并使用条件变量(std::condition_variable)来同步生产者消费者。 设计要点: 1. 共享队列:通常是一个先进先出的队列(例如std::queue),用于存放生产者生产的数据。 2. 互斥锁:用于保护共享队列的访问,防止多个线程同时修改队列。 3. 条件变量:两个条件变量可能更清晰,一个用于生产者(当队列满时等待),一个用于消费者(当队列空时等待)。但在这个例子中,我们通常使用一个条件变量来通知队列状态的变化,然后生产者消费者根据条件检查来决定是否继续等待。然而,为了清晰处理,我们可以使用两个条件变量:一个表示队列不满(用于生产者),一个表示队列不空(用于消费者)。但注意,在简单的实现中,一个条件变量也是可以的,只需要在条件变量通知时使用notify_all(因为条件可能同时满足生产者消费者)。 然而,由于我们同时有多个生产者和多个消费者,为了避免虚假唤醒,我们在条件变量等待时需要使用循环来检查条件。 步骤: 1. 生产者线程在向队列添加数据时: - 先获取互斥锁。 - 然后检查队列是否已满(这里我们可以设置一个最大容量)。如果满了,则等待条件变量(队列不满的条件变量,我们称为not_full)。 - 当队列不满时,向队列添加数据。 - 然后通知消费者(通过队列非空的条件变量not_empty)有数据可以消费。 - 释放锁。 2. 消费者线程从队列取出数据时: - 先获取互斥锁。 - 检查队列是否为空。如果为空,则等待条件变量(队列非空的条件变量not_empty)。 - 当队列非空时,从队列取出数据。 - 然后通知生产者(通过队列不满的条件变量not_full)队列有空间可以生产。 - 释放锁。 但是,注意:条件变量的等待需要配合一个谓词(条件),以避免虚假唤醒。因此,我们使用条件变量的wait方法的重载,它接受一个谓词函数。 我们也可以使用一个条件变量,但这样唤醒的时候会同时唤醒生产者消费者,所以使用两个条件变量更高效。 此外,我们需要一个停止标志,当需要停止所有线程时,设置该标志并通知所有条件变量。 下面是一个详细代码设计: 我们将设计一个线程安全的队列(有最大容量限制),以及多个生产者消费者线程。 头文件:我们需要包含必要的头文件。 代码结构: - 全局定义:最大队列容量,停止标志(atomic bool),互斥锁,条件变量(两个:一个给生产者用,一个给消费者用),共享队列。 - 生产者函数:循环生产数据,直到停止标志被设置。 - 消费者函数:循环消费数据,直到停止标志被设置且队列为空。 注意:停止标志的设置通常由主线程控制(例如,当所有生产者生产完毕,设置停止标志,消费者消费完队列中剩余的数据后退出)。 详细代码: 假设我们生产的数据是整数(从0开始递增,直到某个值)。 我们将创建: - 若干生产者线程(比如2个) - 若干消费者线程(比如3个) 代码步骤: 1. 定义共享数据结构和同步原语。 2. 定义生产者函数。 3. 定义消费者函数。 4. 在main函数中创建线程,等待一段时间(或等待生产者完成),设置停止标志,然后通知所有线程,最后等待所有线程结束。 注意:在设置停止标志后,我们需要调用条件变量的notify_all方法,以便所有等待的线程都能检查到停止标志。 下面是一个示例代码: </think>以下是使用 C++ `std::condition_variable` 实现生产者-消费者模型的详细代码设计,结合线程同步机制确保数据安全: ### 核心组件设计 ```cpp #include <iostream> #include <queue> #include <thread> #include <mutex> #include <condition_variable> #include <atomic> #include <vector> const int BUFFER_SIZE = 10; // 缓冲区最大容量 const int MAX_ITEMS = 100; // 总生产项目数 std::queue<int> buffer; // 共享缓冲区 std::mutex mtx; // 互斥锁保护缓冲区 std::condition_variable not_full; // 缓冲区未满条件变量 std::condition_variable not_empty; // 缓冲区非空条件变量 std::atomic<int> produced_count{0}; // 原子计数器 ``` ### 生产者线程函数 ```cpp void producer(int id) { while (true) { std::unique_lock<std::mutex> lock(mtx); // 等待缓冲区未满(避免虚假唤醒) not_full.wait(lock, [] { return buffer.size() < BUFFER_SIZE; }); // 终止条件检查 if (produced_count >= MAX_ITEMS) { lock.unlock(); not_empty.notify_all(); // 唤醒所有消费者 break; } // 生产数据 int item = produced_count++; buffer.push(item); std::cout << "Producer " << id << " produced: " << item << std::endl; lock.unlock(); not_empty.notify_one(); // 通知一个消费者 } } ``` ### 消费者线程函数 ```cpp void consumer(int id) { while (true) { std::unique_lock<std::mutex> lock(mtx); // 等待缓冲区非空或生产结束 not_empty.wait(lock, [] { return !buffer.empty() || produced_count >= MAX_ITEMS; }); // 终止条件:无数据且生产结束 if (buffer.empty() && produced_count >= MAX_ITEMS) { lock.unlock(); break; } // 消费数据 int item = buffer.front(); buffer.pop(); std::cout << "Consumer " << id << " consumed: " << item << std::endl; lock.unlock(); not_full.notify_one(); // 通知一个生产者 } } ``` ### 主函数(线程管理) ```cpp int main() { const int NUM_PRODUCERS = 3; const int NUM_CONSUMERS = 4; std::vector<std::thread> producers; std::vector<std::thread> consumers; // 创建生产者线程 for (int i = 0; i < NUM_PRODUCERS; ++i) { producers.emplace_back(producer, i); } // 创建消费者线程 for (int i = 0; i < NUM_CONSUMERS; ++i) { consumers.emplace_back(consumer, i); } // 等待生产者结束 for (auto& p : producers) { p.join(); } // 通知消费者生产已结束 not_empty.notify_all(); // 等待消费者结束 for (auto& c : consumers) { c.join(); } std::cout << "All threads completed." << std::endl; return 0; } ``` ### 关键技术点解析 1. **双条件变量机制** - `not_full`:生产者等待缓冲区空间(`buffer.size() < BUFFER_SIZE`) - `not_empty`:消费者等待数据(`!buffer.empty()`) 2. **同步流程** ```mermaid graph LR A[生产者获取锁] --> B{缓冲区满?} B ----> C[等待not_full] B ----> D[生产数据] D --> E[notify_one消费者] F[消费者获取锁] --> G{缓冲区空?} G ----> H[等待not_empty] G ----> I[消费数据] I --> J[notify_one生产者] ``` 3. **终止条件处理** - 生产者:通过原子计数器 `produced_count` 控制总产量 - 消费者:同时检查缓冲区和生产状态,避免死锁 - 主线程使用 `notify_all()` 广播终止信号 4. **性能优化** - 使用 `notify_one()` 而非 `notify_all()` 减少线程争用 - `std::atomic` 保证计数器操作的原子性 - `unique_lock` 自动释放锁机制(RAII) ### 可能遇到的问题及解决方案 1. **虚假唤醒** 使用带谓词的等待:`condition.wait(lock, predicate)` 确保条件真实成立 2. **死锁风险** - 生产者/消费者解锁后才发送通知 - 终止时消费者需同时检查缓冲区和生产状态 3. **数据竞争** 所有共享资源(buffer/counter)访问必须通过互斥锁保护 此设计可安全处理多生产者/消费者场景,通过条件变量实现高效线程同步,避免忙等待消耗 CPU 资源[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值