C++11实现生产者消费者模型
- 生产者消费者模型是什么
简单地举个例子,一个线程(或者进程)在往一个区域(可以使内存或其他地方)写数据,而同时有另外一个线程在取数据,这就存在了一个问题,存储的区域有限,数据存满了写数据的线程怎么办,还有数据空了,取数据的线程怎么处理,这时候就需要该模型来处理了。 - 原理
存储的数据的区域可以用一个队列来模拟,然后我们通过C++11提供的条件变量来通知写入线程在数据不满的时候进行写入,在数据满的时候挂起,对读取线程也是同理。 - 需要C++11前置知识
此次需要用到std::mutex互斥锁,用来防止写入与读取线程竞争数据区域。搭配使用std::unique_lock,利用RAII来控制锁释放,也是因为条件变量的存在需要该类型。
当然还需要最重要的一个std::condition_variable,条件变量。介绍一下几个重要的函数。 - wait()成员函数
void wait( std::unique_lock<std::mutex>& lock );
//Predicate 谓词函数,可以普通函数或者lambda表达式
template< class Predicate >
void wait( std::unique_lock<std::mutex>& lock, Predicate pred );
wait函数会阻塞当前线程直到条件变量被通知,或者被操作系统虚假唤醒(系统Bug),可选地循环知道满足某谓词
- notify成员函数
void notify_one() noexcept;
若任何线程在 *this 上等待,则调用 notify_one 会解阻塞(唤醒)等待线程之一。
void notify_all() noexcept;
若任何线程在 *this 上等待,则解阻塞(唤醒)全部等待线程。
- 实现代码
#include <mutex>
#include <condition_variable>
#include <deque>
#include <vector>
#include <iostream>
#include <thread>
std::mutex g_mtxDegue;
std::mutex g_mtxCout;
std::condition_variable g_cv_not_empty;
std::condition_variable g_cv_not_full;
std::deque<int> g_deque;
int g_itemIndex = 0;
const int g_kItemSize = 30;
const int g_kDequeSize = 10;
void produceItem()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(g_mtxDegue);
g_cv_not_full.wait(lock, []() { return g_deque.size() < g_kDequeSize; });
++g_itemIndex;
g_deque.push_back(g_itemIndex);
{
std::lock_guard<std::mutex> lock_guard(g_mtxCout);
std::cout << "produce item " << g_itemIndex << std::endl;
}
lock.unlock();
g_cv_not_empty.notify_all();
}
void consumeItem()
{
std::this_thread::sleep_for(std::chrono::seconds(2));
std::unique_lock<std::mutex> lock(g_mtxDegue);
g_cv_not_empty.wait(lock, []() { return !g_deque.empty(); });
int itemIndex = g_deque.front();
g_deque.pop_front();
{
std::lock_guard<std::mutex> lock_guard(g_mtxCout);
std::cout << "\tconsume item " << itemIndex << std::endl;
}
lock.unlock();
g_cv_not_full.notify_one();
}
void produceTask()
{
int count = g_kItemSize;
while (count--)
{
produceItem();
}
}
void consumeTask()
{
int count = g_kItemSize;
while (count--)
{
consumeItem();
}
}
//单生产者单消费者模型
void consumeProduceTest()
{
std::vector<std::thread> threads;
threads.push_back(std::thread(produceTask));
threads.push_back(std::thread(consumeTask));
for (int i = 0; i < 2; i++)
threads.at(i).join();
}
这里只是简单地给出了单个生产者和单个消费者的示例,对于单对多、多对单和多对多,需要考虑的是生产者间或者消费者之间对数据数量这个值的互斥。