sc

生产者消费者问题是多线程并发中一个非常经典的问题,相信学过操作系统课程的同学都清楚这个问题的根源。本文将就四种情况分析并介绍生产者和消费者问题,它们分别是:单生产者-单消费者模型,单生产者-多消费者模型,多生产者-单消费者模型,多生产者-多消费者模型,我会给出四种情况下的 C++11 并发解决方案,如果文中出现了错误或者你对代码有异议,欢迎交流 ;-)。
单生产者-单消费者模型
顾名思义,单生产者-单消费者模型中只有一个生产者和一个消费者,生产者不停地往产品库中放入产品,消费者则从产品库中取走产品,产品库容积有限制,只能容纳一定数目的产品,如果生产者生产产品的速度过快,则需要等待消费者取走产品之后,产品库不为空才能继续往产品库中放置新的产品,相反,如果消费者取走产品的速度过快,则可能面临产品库中没有产品可使用的情况,此时需要等待生产者放入一个产品后,消费者才能继续工作。C++11实现单生产者单消费者模型的代码如下:

#include <unistd.h>#include <cstdlib>#include <condition_variable>#include <iostream>#include <mutex>#include <thread>static const int kItemRepositorySize = 10; // Item buffer size.static const int kItemsToProduce = 1000; // How many items we plan to produce.struct ItemRepository { int item_buffer[kItemRepositorySize]; // 产品缓冲区, 配合 read_position 和 write_position 模型环形队列. size_t read_position; // 消费者读取产品位置. size_t write_position; // 生产者写入产品位置. std::mutex 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::cout << "Producer is waiting for an empty slot...\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::cout << "Consumer is waiting for items...\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) { // sleep(1); std::cout << "Produce the " << i << "^th item..." << std::endl; ProduceItem(&gItemRepository, i); // 循环生产 kItemsToProduce 个产品. }}void ConsumerTask() // 消费者任务{ static int cnt = 0; while(1) { sleep(1); int item = ConsumeItem(&gItemRepository); // 消费一个产品. std::cout << "Consume the " << item << "^th item" << std::endl; if (++cnt == kItemsToProduce) break; // 如果产品消费个数为 kItemsToProduce, 则退出. }}void InitItemRepository(ItemRepository *ir){ ir->write_position = 0; // 初始化产品写入位置. ir->read_position = 0; // 初始化产品读取位置.}int main(){ InitItemRepository(&gItemRepository); std::thread producer(ProducerTask); // 创建生产者线程. std::thread consumer(ConsumerTask); // 创建消费之线程. producer.join(); consumer.join();}

单生产者-多消费者模型
与单生产者和单消费者模型不同的是,单生产者-多消费者模型中可以允许多个消费者同时从产品库中取走产品。所以除了保护产品库在多个读写线程下互斥之外,还需要维护消费者取走产品的计数器,代码如下:


复制代码

include <unistd.h>#include <cstdlib>#include <condition_variable>#include <iostream>#include <mutex>#include <thread>static const int kItemRepositorySize = 4; // Item buffer size.static const int kItemsToProduce = 10; // How many items we plan to produce.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::cout << "Producer is waiting for an empty slot...\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::cout << "Consumer is waiting for items...\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) { // sleep(1); std::cout << "Producer thread " << std::this_thread::get_id() << " producing the " << i << "^th item..." << std::endl; ProduceItem(&gItemRepository, i); } std::cout << "Producer thread " << std::this_thread::get_id() << " is exiting..." << std::endl;}void ConsumerTask(){ bool ready_to_exit = false; while(1) { sleep(1); std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx); if (gItemRepository.item_counter < kItemsToProduce) { int item = ConsumeItem(&gItemRepository); ++(gItemRepository.item_counter); std::cout << "Consumer thread " << std::this_thread::get_id() << " is consuming the " << item << "^th item" << std::endl; } else ready_to_exit = true; lock.unlock(); if (ready_to_exit == true) break; } std::cout << "Consumer thread " << std::this_thread::get_id() << " is exiting..." << 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();}

复制代码

多生产者-单消费者模型
与单生产者和单消费者模型不同的是,多生产者-单消费者模型中可以允许多个生产者同时向产品库中放入产品。所以除了保护产品库在多个读写线程下互斥之外,还需要维护生产者放入产品的计数器,代码如下:


复制代码

include <unistd.h>#include <cstdlib>#include <condition_variable>#include <iostream>#include <mutex>#include <thread>static const int kItemRepositorySize = 4; // Item buffer size.static const int kItemsToProduce = 10; // How many items we plan to produce.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::cout << "Producer is waiting for an empty slot...\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::cout << "Consumer is waiting for items...\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(){ bool ready_to_exit = false; while(1) { sleep(1); std::unique_lock<std::mutex> lock(gItemRepository.item_counter_mtx); if (gItemRepository.item_counter < kItemsToProduce) { ++(gItemRepository.item_counter); ProduceItem(&gItemRepository, gItemRepository.item_counter); std::cout << "Producer thread " << std::this_thread::get_id() << " is producing the " << gItemRepository.item_counter << "^th item" << std::endl; } else ready_to_exit = true; lock.unlock(); if (ready_to_exit == true) break; } std::cout << "Producer thread " << std::this_thread::get_id() << " is exiting..." << std::endl;}void ConsumerTask(){ static int item_consumed = 0; while(1) { sleep(1); ++item_consumed; if (item_consumed <= kItemsToProduce) { int item = ConsumeItem(&gItemRepository); std::cout << "Consumer thread " << std::this_thread::get_id() << " is consuming the " << item << "^th item" << std::endl; } else break; } std::cout << "Consumer thread " << std::this_thread::get_id() << " is exiting..." << std::endl;}void InitItemRepository(ItemRepository *ir){ ir->write_position = 0; ir->read_position = 0; ir->item_counter = 0;}int main(){ InitItemRepository(&gItemRepository); std::thread producer1(ProducerTask); std::thread producer2(ProducerTask); std::thread producer3(ProducerTask); std::thread producer4(ProducerTask); std::thread consumer(ConsumerTask); producer1.join(); producer2.join(); producer3.join(); producer4.join(); consumer.join();}

[图片上传中。。。(6)]

多生产者-多消费者模型
该模型可以说是前面两种模型的综合,程序需要维护两个计数器,分别是生产者已生产产品的数目和消费者已取走产品的数目。另外也需要保护产品库在多个生产者和多个消费者互斥地访问。
代码如下:
[图片上传中。。。(7)]

include <unistd.h>#include <cstdlib>#include <condition_variable>#include <iostream>#include <mutex>#include <thread>static const int kItemRepositorySize = 4; // Item buffer size.static const int kItemsToProduce = 10; // How many items we plan to produce.struct ItemRepository { int item_buffer[kItemRepositorySize]; size_t read_position; size_t write_position; size_t produced_item_counter; size_t consumed_item_counter; std::mutex mtx; std::mutex produced_item_counter_mtx; std::mutex consumed_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::cout << "Producer is waiting for an empty slot...\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::cout << "Consumer is waiting for items...\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(){ bool ready_to_exit = false; while(1) { sleep(1); std::unique_lock<std::mutex> lock(gItemRepository.produced_item_counter_mtx); if (gItemRepository.produced_item_counter < kItemsToProduce) { ++(gItemRepository.produced_item_counter); ProduceItem(&gItemRepository, gItemRepository.produced_item_counter); std::cout << "Producer thread " << std::this_thread::get_id() << " is producing the " << gItemRepository.produced_item_counter << "^th item" << std::endl; } else ready_to_exit = true; lock.unlock(); if (ready_to_exit == true) break; } std::cout << "Producer thread " << std::this_thread::get_id() << " is exiting..." << std::endl;}void ConsumerTask(){ bool ready_to_exit = false; while(1) { sleep(1); std::unique_lock<std::mutex> lock(gItemRepository.consumed_item_counter_mtx); if (gItemRepository.consumed_item_counter < kItemsToProduce) { int item = ConsumeItem(&gItemRepository); ++(gItemRepository.consumed_item_counter); std::cout << "Consumer thread " << std::this_thread::get_id() << " is consuming the " << item << "^th item" << std::endl; } else ready_to_exit = true; lock.unlock(); if (ready_to_exit == true) break; } std::cout << "Consumer thread " << std::this_thread::get_id() << " is exiting..." << std::endl;}void InitItemRepository(ItemRepository *ir){ ir->write_position = 0; ir->read_position = 0; ir->produced_item_counter = 0; ir->consumed_item_counter = 0;}int main(){ InitItemRepository(&gItemRepository); std::thread producer1(ProducerTask); std::thread producer2(ProducerTask); std::thread producer3(ProducerTask); std::thread producer4(ProducerTask); std::thread consumer1(ConsumerTask); std::thread consumer2(ConsumerTask); std::thread consumer3(ConsumerTask); std::thread consumer4(ConsumerTask); producer1.join(); producer2.join(); producer3.join(); producer4.join(); consumer1.join(); consumer2.join(); consumer3.join(); consumer4.join();}

[图片上传中。。。(8)]

另外,所有例子的代码(包括前面一些指南的代码均放在github上),希望对大家学习 C++11 多线程并发有所帮助

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容