C++でProducer-Consumerパターン書いてみた
↓この本の第5章 Producer-ConsumerパターンをC++で書いてみた。
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (203件) を見る
処理の流れとしては、ケーキを作る人(スレッド)が3人、ケーキを食べる人が3人、作ったケーキを置いておくテーブルの面積がケーキ3つ分、という風になっています。面倒くさいのでファイルは分割しませんでしたが、本のサンプルと対比はし易いかと思います。
#include <iostream> #include <chrono> #include <atomic> #include <thread> #include <condition_variable> #include <mutex> #include <string> #include <sstream> #include <random> #include <queue> class table { std::mutex mutex_; std::condition_variable condition_; std::queue<std::string> buffer_; const int count_; public: explicit table(int count) : buffer_{}, count_{count} { } table(table const&) = delete; table& operator=(table const&) = delete; void push(std::string const& cake, std::string const& thread_name) { std::unique_lock<std::mutex> lock{mutex_}; while (static_cast<int>(buffer_.size()) >= count_) { condition_.wait(lock); } buffer_.push(cake); condition_.notify_all(); std::cout << thread_name << " puts " << cake << std::endl; } std::string pop(std::string const& thread_name) { std::unique_lock<std::mutex> lock{mutex_}; while (buffer_.empty()) { condition_.wait(lock); } std::string cake = std::move(buffer_.front()); buffer_.pop(); condition_.notify_all(); std::cout << thread_name << " takes " << cake << std::endl; return cake; } }; class cake_maker { table& table_; std::string name_; std::uniform_int_distribution<> rand_; std::mt19937 engine_; static std::atomic<int> id_; public: cake_maker(table& table, std::string name, std::mt19937::result_type seed) : table_{table}, name_{std::move(name)}, rand_{0, 1000}, engine_{seed} { } void operator()() { while (true) { std::this_thread::sleep_for(std::chrono::milliseconds{rand_(engine_)}); std::ostringstream oss; oss << "[cake no." << id_ << "] by " << name_; ++id_; table_.push(oss.str(), name_); } } }; std::atomic<int> cake_maker::id_ = 0; class cake_eater { table& table_; std::string name_; std::uniform_int_distribution<> rand_; std::mt19937 engine_; public: cake_eater(table& table, std::string name, std::mt19937::result_type seed) : table_{table}, name_{std::move(name)}, rand_{0, 1000}, engine_{seed} { } void operator()() { while (true) { std::string const& cake = table_.pop(name_); std::this_thread::sleep_for(std::chrono::milliseconds{rand_(engine_)}); } } }; int main() { table table{10}; std::thread maker1{cake_maker{table, "Maker-1", 31415}}; std::thread maker2{cake_maker{table, "Maker-2", 92653}}; std::thread maker3{cake_maker{table, "Maker-3", 58979}}; std::thread eater1{cake_eater{table, "Eater-1", 32384}}; std::thread eater2{cake_eater{table, "Eater-2", 62643}}; std::thread eater3{cake_eater{table, "Eater-3", 38327}}; maker1.join(); maker2.join(); maker3.join(); eater1.join(); eater2.join(); eater3.join(); }
実行例
Maker-2 puts [cake no.0] by Maker-2 Eater-3 takes [cake no.0] by Maker-2 Maker-2 puts [cake no.1] by Maker-2 Eater-3 takes [cake no.1] by Maker-2 Maker-2 puts [cake no.2] by Maker-2 Eater-2 takes [cake no.2] by Maker-2 Maker-1 puts [cake no.3] by Maker-1 Eater-1 takes [cake no.3] by Maker-1 Maker-3 puts [cake no.4] by Maker-3 Eater-3 takes [cake no.4] by Maker-3 Maker-3 puts [cake no.5] by Maker-3 Eater-3 takes [cake no.5] by Maker-3 Maker-1 puts [cake no.6] by Maker-1 Maker-1 puts [cake no.7] by Maker-1 Eater-2 takes [cake no.6] by Maker-1 Eater-1 takes [cake no.7] by Maker-1 Maker-3 puts [cake no.8] by Maker-3 Maker-2 puts [cake no.9] by Maker-2 ^C続行するには何かキーを押してください . . .
C++標準でスレッドに名前をつける方法がなかったので、苦肉の策として、push
とpop
の引数にスレッド名を渡す引数を追加しました。
while (static_cast<int>(buffer_.size()) >= count_) { condition_.wait(lock); }
と
while (buffer_.empty()) {
condition_.wait(lock);
}
は
condition_.wait(lock, [this] { return static_cast<int>(buffer_.size()) < count_; });
と
condition_.wait(lock, [this] { return !buffer_.empty(); })
とも書けますが、本との比較を意識して、whileで書きました。
https://sites.google.com/site/cpprefjp/article/how_to_use_cv
にはnot_empty_
とnot_full_
の2つの条件変数に分かれていますが、何故なんでしょうか?
1つでも正しく動いてると思いますが、そこが分からなかったです。
(追記こちらを参照
条件変数 Step-by-Step入門 - yohhoyの日記(別館)
)
このプログラムは無限ループなので、Ctrl + Cなどで強制終了して下さい。