前言:上篇博文(点击这里)介绍了生产者消费者模式中的单生产单消费模式,本篇博文介绍单生产多消费者模式,下面还会介绍多生产单消费模式、多生产多消费模式。代码类似,所以类似的博客只介绍差异性,本篇纯属小白教学,如有不对,还望指正。如有疑问,可交流学习。
#include <cstdlib>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
static const int kItemRepositorySize = 5; // Item buffer size.
static const int kItemsToProduce = 10; // How many items we plan to produce.
std::mutex mutex;//多线程标准输出同步锁
struct ItemRepository
{
int item_buffer[kItemRepositorySize];
size_t read_position;
size_t write_position;
size_t item_counter;
std::mutex mtx;
std::mutex item_counter_mtx;
std::condition_variable repo_not_full;
std::condition_variable repo_not_empty;
} gItemRepository;
typedef struct ItemRepository ItemRepository;
void ProduceItem(ItemRepository *ir, int item)
{
std::unique_lock<std::mutex> lock(ir->mtx);
while (((ir->write_position + 1) % kItemRepositorySize)
== ir->read_position)
{
// item buffer is full, just wait here.
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "缓冲区满,等待缓冲区不满\n";
}
(ir->repo_not_full).wait(lock);
}
(ir->item_buffer)[ir->write_position] = item;
(ir->write_position)++;
if (ir->write_position == kItemRepositorySize)
ir->write_position = 0;
(ir->repo_not_empty).notify_all();
lock.unlock();
}
int ConsumeItem(ItemRepository *ir)
{
int data;
std::unique_lock<std::mutex> lock(ir->mtx);
// item buffer is empty, just wait here.
while (ir->write_position == ir->read_position)
{
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "缓冲区空,等待生产者生产产品\n";
}
(ir->repo_not_empty).wait(lock);
}
data = (ir->item_buffer)[ir->read_position];
(ir->read_position)++;
if (ir->read_position >= kItemRepositorySize)
ir->read_position = 0;
(ir->repo_not_full).notify_all();
lock.unlock();
return data;
}
void ProducerTask()
{
for (int i = 1; i <= kItemsToProduce; ++i)
{
ProduceItem(&gItemRepository, i);
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "生产线程" << std::this_thread::get_id()
<< "生产第" << i << "个产品" << std::endl;
}
}
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "生产线程" << std::this_thread::get_id()
<< "退出" << std::endl;
}
}
void ConsumerTask()
{
bool ready_to_exit = false;
int cent = 0;
while (1)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx);
if (gItemRepository.item_counter < kItemsToProduce)
{
int item = ConsumeItem(&gItemRepository);
++(gItemRepository.item_counter);
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "消费线程" << std::this_thread::get_id()
<< "正在消费第" << item << "个产品" << std::endl;
}
}
else
{
ready_to_exit = true;
}
lock.unlock();
if (ready_to_exit == true)
{
break;
}
}
{
std::lock_guard<std::mutex> lock(mutex);
std::cout << "消费线程" << std::this_thread::get_id()
<< "退出" << std::endl;
}
}
void InitItemRepository(ItemRepository *ir)
{
ir->write_position = 0;
ir->read_position = 0;
ir->item_counter = 0;
}
int main()
{
InitItemRepository(&gItemRepository);
std::thread producer(ProducerTask);
std::thread consumer1(ConsumerTask);
std::thread consumer2(ConsumerTask);
std::thread consumer3(ConsumerTask);
std::thread consumer4(ConsumerTask);
producer.join();
consumer1.join();
consumer2.join();
consumer3.join();
consumer4.join();
return 0;
}
几点解释,大佬绕行。
有同学可能会想到,我在单生产单消费的main函数中加上几个消费者不就可以了吗???下面解释一下为什么不可以。从上篇你可以看出,当生产者生产数量等于给定的产品数量kItemsToProduce时候,生产者函数会将ir->write_position = 0;,在消费者线程中如果消费全部商品后会将ir->read_position = 0;,若只是简单的加上几个线程,会出现某一个线程消费完最后一个产品的时候,由于ir->read_position是全局变量,所以另外几个消费者线程会进入while (ir->write_position == ir->read_position)循环中,按照预定的生产产品数量,生产者已经生产结束。消费者却进入此循环出不来,一直等待生产者生产,从而造成消费者线程永不会退出,这样会消费大量的系统资源,导致系统崩溃。
从main函数可以看到,有一个生产者和四个消费者线程,这也就是体现了单生产多消费模式。代码和单生产单消费相似,最大的不同是多了一个准备退出ready_to_exit标志位。下面简单讲讲为什么会多出这个操作。
不知你是否注意到上篇单生产单消费模式博文中,在ConsumerTask函数中定义了一个局部变量cnt,消费一个产品,变量加1,直到消费数量达到生产的总数之后,break退出消费者线程。
但是在单生产多消费模式中,出现了多个消费者线程,多线程公用消费者模式,所以要进行加锁处理std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx);,也就是说当一个线程消费某一个具体产品的时候,要保证这个产品就不能被其他消费者线程消费。由于线程之间是并行运行的,不知道是哪一个线程消费最后一个产品,所以当消费完最后一个产品之后,将标志位置为true,解锁之后,退出消费者线程。