狠狠撸

狠狠撸Share a Scribd company logo
Effective Modern C++ 勉強会
Item39
近藤 貴俊
2015/10/4 1
今回紹介するItem
? Item 39:Consider void futures for one-shot
event communication.
? ワンショットイベントにvoid futureを使うことを
考えてみよう。
2015/10/4 2
2015/10/4 3
std::condition_variable cv;
std::mutex m;
cv.notify_one();
Item39
...
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk);
...
}
...
イベント用条件変数
cvと一緒に使うmutex
cvを通してイベント通知
イベントを通知する側
イベントを受ける側
mutexをlock
ここで通知を待つ
イベントを受けての処理(mはlock中)
イベントを受けての処理(mはlock解除されている)
このコードには問題がある
2015/10/4 4
std::condition_variable cv;
std::mutex m;
cv.notify_one();
Item39
...
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk);
...
}
...
イベント用条件変数
cvと一緒に使うmutex
cvを通してイベント通知
イベントを通知する側
イベントを受ける側
mutexをlock
ここで通知を待つ
イベントを受けての処理(mはlock中)
イベントを受けての処理(mはlock解除されている)
問題その1
1
2
3
3 でブロックしてしまう
2015/10/4 5
std::condition_variable cv;
std::mutex m;
完了しておくべき処理A
cv.notify_one();
Item39
...
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk);
処理Aが完了していることを期待
}
...
イベント用条件変数
cvと一緒に使うmutex
イベントを通知する側
イベントを受ける側
ここで通知を待つ
問題その2 spurious wakeup
3
1
3 で処理Aが完了していない
2
3
Sprious wakeupとリオーダーは無関係
次ページで訂正
2015/10/4 6
std::condition_variable cv;
std::mutex m;
Item39
...
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk);
}
...
イベント用条件変数
cvと一緒に使うmutex
イベントを通知する側
イベントを受ける側
ここで通知を待つ
問題その2 spurious wakeup
1
1 でnotifyされていないのに、ブロックが解除される。
http://d.hatena.ne.jp/yohhoy/20120326/p1 参照
2015/10/4 7
Item39
...
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk);
...
}
cv.wait(lk,
[]{ return whether the event has occurred; });
イベントを受ける側
ラムダ式を引数に取るversionのwaitを使う
本当にイベントが発生したかどうか確認する。
発生していたらtrue
このアプローチの詳細は後述
その前に
2015/10/4 8
Item39
flag = true;
フラグを用いたポーリングベースのアプローチ
std::atomic<bool> flag(false); atomic変数
while(!flag);
イベント通知
イベントを受けての処理
?mutex不要
?whileループの前にフラグがセットされても問題ない
?spurious wakeupの問題も無い
?ループが回り続けるため、CPUリソースを消費する
そこで、条件変数とフラグベースアプローチを組み合わせる
イベントを受ける側
イベントを通知する側
2015/10/4 9
Item39
...
{
std::unique_lock<std::mutex> lk(m);
flag = true;
}
cv.notify_one();
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{ return flag; });
...
}
...
イベントを通知する側
ロックされた区間でフラグを設定
std::condition_variable cv;
std::mutex m;
bool flag(false); flag操作はmutexでロックされた区間で行われるので
atomicでなくてよい
条件変数でイベント通知
イベントを受ける側
本当にイベントが発生したかどうか確認する。
発生していたらtrue
問題は全て解決する がコードはそこそこ複雑
この例ではflagはglobal
よってキャプチャ不要
2015/10/4 10
Item39
p.set_value();
std::promise<void> p; voidのpromiseを準備
p.get_future().wait();
イベント通知
イベントを受けての処理
future/promiseを使ったアプローチ
イベントを待つ
2015/10/4 11
Item39
実際にスレッドを使ったコード
std::promise<void> p;
void react();
void detect()
{
std::thread t([]
{
p.get_future().wait();
react();
});
...
p.set_value();
...
t.join();
}
イベントを待つ側のスレッド
イベントを送る側のスレッド
イベントを受けての処理
tをunjoinableに Item37参照
この例ではpはglobal
よってキャプチャ不要
2015/10/4 12
Item39
future/promiseを使ったアプローチ
std::promise<void> p;
void react();
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
...
p.set_value();
...
}
Item37のThreadRAIIを使えばシンプルに?
2015/10/4 13
Item39
future/promiseを使ったアプローチ
std::promise<void> p;
void react();
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
...
p.set_value();
...
}
Item37のThreadRAIIを使えばシンプルに?
ここで例外が発生したら
2015/10/4 14
Item39
future/promiseを使ったアプローチ
std::promise<void> p;
void react();
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
...
p.set_value();
...
}
Item37のThreadRAIIを使えばシンプルに?
ここで例外が発生したら スレッドは待ったまま
set_value()は呼ばれない
2015/10/4 15
Item39
future/promiseを使ったアプローチ
std::promise<void> p;
void react();
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
...
p.set_value();
...
}
Item37のThreadRAIIを使えばシンプルに?
ここで例外が発生したら スレッドは待ったまま
set_value()は呼ばれない
ThreadRAIIのデストラクタでjoinするため、そこでブロックし、
ThreadRAIIのデストラクタが永久に終わらない
2015/10/4 16
Item39
future/promiseを使ったアプローチ
std::promise<void> p;
void react();
void detect()
{
ThreadRAII tr(
std::thread([]
{
p.get_future().wait();
react();
}),
ThreadRAII::DtorAction::join
);
{
scope_exit se([] {p.set_value();});
...
}
...
}
struct scope_exit {
scope_exit(std::function<void (void)> f)
: f_(std::move(f)) {}
~scope_exit(void) { f_(); }
private:
std::function<void (void)> f_;
};
http://melpon.org/wandbox/permlink/NdFo1yH7d9t0O2FL
2015/10/4 17
Item39
future/promiseを使ったアプローチ(スレッドが複数の場合)
std::promise<void> p;
void detect()
{
auto sf = p.get_future().share();
std::vector<std::thread> vt;
for (int i = 0; i < threadsToRun; ++i) {
vt.emplace_back([sf]{ sf.wait();
react(); });
}
...
p.set_value();
...
for (auto& t : vt) {
t.join();
}
}
std::shared_futureを使う
例外を検知して p.set_value()する処理は必要
2015/10/4 18
Item39
? シンプルなイベント通知を条件変数を用いて実現する場合、
mutexとイベントが発生したかのチェックが必要になる。
? この問題を回避するためにフラグを導入すれば良いが、
ポーリングベースだとブロックしない
? 条件変数とフラグを組み合わせは、
通知メカニズムを堅苦しいもの(stilted)にする
? std::promiseとfutureの組み合わせでこの問題を解決できる
が、shared stateのためのheap memoryを必要とし、また、一
度きりの通知にしか使えない
Things to Remember

More Related Content

Effective Modern C++ study group Item39

  • 1. Effective Modern C++ 勉強会 Item39 近藤 貴俊 2015/10/4 1
  • 2. 今回紹介するItem ? Item 39:Consider void futures for one-shot event communication. ? ワンショットイベントにvoid futureを使うことを 考えてみよう。 2015/10/4 2
  • 3. 2015/10/4 3 std::condition_variable cv; std::mutex m; cv.notify_one(); Item39 ... { std::unique_lock<std::mutex> lk(m); cv.wait(lk); ... } ... イベント用条件変数 cvと一緒に使うmutex cvを通してイベント通知 イベントを通知する側 イベントを受ける側 mutexをlock ここで通知を待つ イベントを受けての処理(mはlock中) イベントを受けての処理(mはlock解除されている) このコードには問題がある
  • 4. 2015/10/4 4 std::condition_variable cv; std::mutex m; cv.notify_one(); Item39 ... { std::unique_lock<std::mutex> lk(m); cv.wait(lk); ... } ... イベント用条件変数 cvと一緒に使うmutex cvを通してイベント通知 イベントを通知する側 イベントを受ける側 mutexをlock ここで通知を待つ イベントを受けての処理(mはlock中) イベントを受けての処理(mはlock解除されている) 問題その1 1 2 3 3 でブロックしてしまう
  • 5. 2015/10/4 5 std::condition_variable cv; std::mutex m; 完了しておくべき処理A cv.notify_one(); Item39 ... { std::unique_lock<std::mutex> lk(m); cv.wait(lk); 処理Aが完了していることを期待 } ... イベント用条件変数 cvと一緒に使うmutex イベントを通知する側 イベントを受ける側 ここで通知を待つ 問題その2 spurious wakeup 3 1 3 で処理Aが完了していない 2 3 Sprious wakeupとリオーダーは無関係 次ページで訂正
  • 6. 2015/10/4 6 std::condition_variable cv; std::mutex m; Item39 ... { std::unique_lock<std::mutex> lk(m); cv.wait(lk); } ... イベント用条件変数 cvと一緒に使うmutex イベントを通知する側 イベントを受ける側 ここで通知を待つ 問題その2 spurious wakeup 1 1 でnotifyされていないのに、ブロックが解除される。 http://d.hatena.ne.jp/yohhoy/20120326/p1 参照
  • 7. 2015/10/4 7 Item39 ... { std::unique_lock<std::mutex> lk(m); cv.wait(lk); ... } cv.wait(lk, []{ return whether the event has occurred; }); イベントを受ける側 ラムダ式を引数に取るversionのwaitを使う 本当にイベントが発生したかどうか確認する。 発生していたらtrue このアプローチの詳細は後述 その前に
  • 8. 2015/10/4 8 Item39 flag = true; フラグを用いたポーリングベースのアプローチ std::atomic<bool> flag(false); atomic変数 while(!flag); イベント通知 イベントを受けての処理 ?mutex不要 ?whileループの前にフラグがセットされても問題ない ?spurious wakeupの問題も無い ?ループが回り続けるため、CPUリソースを消費する そこで、条件変数とフラグベースアプローチを組み合わせる イベントを受ける側 イベントを通知する側
  • 9. 2015/10/4 9 Item39 ... { std::unique_lock<std::mutex> lk(m); flag = true; } cv.notify_one(); { std::unique_lock<std::mutex> lk(m); cv.wait(lk, []{ return flag; }); ... } ... イベントを通知する側 ロックされた区間でフラグを設定 std::condition_variable cv; std::mutex m; bool flag(false); flag操作はmutexでロックされた区間で行われるので atomicでなくてよい 条件変数でイベント通知 イベントを受ける側 本当にイベントが発生したかどうか確認する。 発生していたらtrue 問題は全て解決する がコードはそこそこ複雑 この例ではflagはglobal よってキャプチャ不要
  • 10. 2015/10/4 10 Item39 p.set_value(); std::promise<void> p; voidのpromiseを準備 p.get_future().wait(); イベント通知 イベントを受けての処理 future/promiseを使ったアプローチ イベントを待つ
  • 11. 2015/10/4 11 Item39 実際にスレッドを使ったコード std::promise<void> p; void react(); void detect() { std::thread t([] { p.get_future().wait(); react(); }); ... p.set_value(); ... t.join(); } イベントを待つ側のスレッド イベントを送る側のスレッド イベントを受けての処理 tをunjoinableに Item37参照 この例ではpはglobal よってキャプチャ不要
  • 12. 2015/10/4 12 Item39 future/promiseを使ったアプローチ std::promise<void> p; void react(); void detect() { ThreadRAII tr( std::thread([] { p.get_future().wait(); react(); }), ThreadRAII::DtorAction::join ); ... p.set_value(); ... } Item37のThreadRAIIを使えばシンプルに?
  • 13. 2015/10/4 13 Item39 future/promiseを使ったアプローチ std::promise<void> p; void react(); void detect() { ThreadRAII tr( std::thread([] { p.get_future().wait(); react(); }), ThreadRAII::DtorAction::join ); ... p.set_value(); ... } Item37のThreadRAIIを使えばシンプルに? ここで例外が発生したら
  • 14. 2015/10/4 14 Item39 future/promiseを使ったアプローチ std::promise<void> p; void react(); void detect() { ThreadRAII tr( std::thread([] { p.get_future().wait(); react(); }), ThreadRAII::DtorAction::join ); ... p.set_value(); ... } Item37のThreadRAIIを使えばシンプルに? ここで例外が発生したら スレッドは待ったまま set_value()は呼ばれない
  • 15. 2015/10/4 15 Item39 future/promiseを使ったアプローチ std::promise<void> p; void react(); void detect() { ThreadRAII tr( std::thread([] { p.get_future().wait(); react(); }), ThreadRAII::DtorAction::join ); ... p.set_value(); ... } Item37のThreadRAIIを使えばシンプルに? ここで例外が発生したら スレッドは待ったまま set_value()は呼ばれない ThreadRAIIのデストラクタでjoinするため、そこでブロックし、 ThreadRAIIのデストラクタが永久に終わらない
  • 16. 2015/10/4 16 Item39 future/promiseを使ったアプローチ std::promise<void> p; void react(); void detect() { ThreadRAII tr( std::thread([] { p.get_future().wait(); react(); }), ThreadRAII::DtorAction::join ); { scope_exit se([] {p.set_value();}); ... } ... } struct scope_exit { scope_exit(std::function<void (void)> f) : f_(std::move(f)) {} ~scope_exit(void) { f_(); } private: std::function<void (void)> f_; }; http://melpon.org/wandbox/permlink/NdFo1yH7d9t0O2FL
  • 17. 2015/10/4 17 Item39 future/promiseを使ったアプローチ(スレッドが複数の場合) std::promise<void> p; void detect() { auto sf = p.get_future().share(); std::vector<std::thread> vt; for (int i = 0; i < threadsToRun; ++i) { vt.emplace_back([sf]{ sf.wait(); react(); }); } ... p.set_value(); ... for (auto& t : vt) { t.join(); } } std::shared_futureを使う 例外を検知して p.set_value()する処理は必要
  • 18. 2015/10/4 18 Item39 ? シンプルなイベント通知を条件変数を用いて実現する場合、 mutexとイベントが発生したかのチェックが必要になる。 ? この問題を回避するためにフラグを導入すれば良いが、 ポーリングベースだとブロックしない ? 条件変数とフラグを組み合わせは、 通知メカニズムを堅苦しいもの(stilted)にする ? std::promiseとfutureの組み合わせでこの問題を解決できる が、shared stateのためのheap memoryを必要とし、また、一 度きりの通知にしか使えない Things to Remember