>100 Views
August 19, 15
スライド概要
第八回 Effective Modern C++ 勉強会の資料です
Item 37
サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。
Effective Modern C++ 勉強会 #8 Item 37 内田 公太 (@uchan_nos) サイボウズ株式会社 2015/08/19
アジェンダ • Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう すべての std::thread は joinable / unjoinable のどちらかの状態 • 「joinable な std::thread」は裏にあるスレッド が実行中か実行可能な状態になっているスレッドに対 応する • ブロック中かスケジュールを待っている状態のスレッド • まさに実行中のスレッド • 「unjoinable な std::thread」は joinable でな いスレッドのこと
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう unjoinable になるパターン • デフォルトコンストラクタで生成した std::thread • 何も実行するものがなく裏のスレッドに紐づかない • 他のオブジェクトに move 済みの std::thread • move の結果、裏のスレッドが他の std::thread に移る • join 済みの std::thread • join すると裏のスレッドも実行を停止し、切り離される • detach 済みの std::thread • detach すると裏のスレッドとの接続が切れる
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう なぜ join 可能かどうかが重要なのか → joinable な std::thread がデストラクトされると プログラムの実行が終了してしまうから。
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
constexpr auto tenMillion = 10000000;
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
if (conditionsAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
constexpr auto tenMillion = 10000000;
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
if (conditionsAreSatisfied()) {
t.join();
重い処理
performComputation(goodVals);
→並列実行
return true;
}
return false;
}
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
constexpr auto tenMillion = 10000000;
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
if (conditionsAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
joinable な t がデスト
return false;
ラクトされる系がある
}
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう joinable な std::thread がデストラクトされると プログラムの実行が終了してしまうから。 → デストラクタで対策すれば良いのでは? • 暗黙に join してあげれば良さそう • しかし追跡しずらい性能上の問題が出るかも。例えば conditionsAreSatisfied() が既に false を返したのに フィルタが終わるのを待ち続けるとか。 • 暗黙に detach してあげれば良い? • 裏で動くスレッドが切り離された後も実行を続けるので、いろい ろ良くないことが起きる。 • 例えば・・・(次ページ)
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
constexpr auto tenMillion = 10000000;
bool doWork(...)
ローカル変数
{
std::vector<int> goodVals;
への参照
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
...
}
将来他の関数のスタックフレームが doWork のスタックフレーム
だった領域(goodVals があった領域)まで到達すると、
スタック領域が自然に書き換わるように見える
これをデバッグする楽しさといったら無いよ!
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう joinable な std::thread がデストラクトされると プログラムの実行が終了してしまうから。 → デストラクタで対策すれば良いのでは? • 標準規格では join か detach のどちらも採用せず、 単にプログラムを終了することになった • 全パスで確実に unjoinable にするために、独自の RAII クラスを書いて対策しよう! • std::unique_ptr とか std::shared_ptr とか std::fstream とか標準にはたくさんの RAII があるけど、 残念ながら std::thread に対する RAII クラスは無い…
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう class ThreadRAII { public: enum class DtorAction { join, detach }; ThreadRAII(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {} ~ThreadRAII() { if (t.joinable()) { if (action == DtorAction::join) { t.join(); } else { t.detach(); } } } std::thread& get() { return t; } private: DtorAction action; std::thread t; };
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう class ThreadRAII { public: が先だが enum class 引数の順序は DtorAction { thread join, detach }; ThreadRAII(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {} ~ThreadRAII() { if (t.joinable()) { if (action == DtorAction::join) { t.join(); } else { t.detach(); } } } std::thread& get() { return t; } private: DtorAction action; 宣言の順序は thread が後 std::thread t; };
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう class ThreadRAII { public: が先だが enum class 引数の順序は DtorAction { thread join, detach }; ThreadRAII(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {} ~ThreadRAII() { thread は初期化されるとすぐ動き if (t.joinable()) { 出すので、クラスの一番後ろで宣言 if (action == DtorAction::join) { するのは良い習慣。 t.join(); } else { t.detach(); スレッドオブジェクトが構築された } ときには、他のデータメンバが初期 } 化されているのを保証できる。 } std::thread& get() { return t; } private: DtorAction action; 宣言の順序は thread が後 std::thread t; };
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう if (t.joinable()) { if (action == DtorAction::join) { t.join(); } else { マルチスレッドで t.detach(); 競合しそう… } } • joinable() の検査は必要 • unjoinable スレッドに対して join/detach は未定義動作 • 検査と join/detach の間に隙間があるので競合? • メンバ関数呼び出しを通してのみ unjoinable になれる • → ThreadRAII がデストラクトされるときには、他のスレッ ドはメンバ関数を呼び出せないはずである。
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
std::thread t([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
});
auth nh = t.native_handle();
...
if (conditionsAreSatisfied()) {
t.join();
performComputation(goodVals);
return true;
}
return false;
}
最初のコード
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
bool doWork(std::function<bool(int)> filter,
int maxVal = tenMillion)
{
std::vector<int> goodVals;
ThreadRAII t(
std::thread([&filter, maxVal, &goodVals]
{
for (auto i = 0; i <= maxVal; ++i)
{ if (filter(i)) goodVals.push_back(i); }
}),
ThreadRAII::DtorAction::join
);
auth nh = t.get().native_handle();
...
if (conditionsAreSatisfied()) {
t.get().join();
performComputation(goodVals);
return true;
}
return false;
}
ThreadRAII を使ったコード
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう class ThreadRAII { public: enum class DtorAction { join, detach }; ThreadRAII(std::thread&& t, DtorAction a) : action(a), t(std::move(t)) {} ~ThreadRAII() { … } ThreadRAII(ThreadRAII&&) = default; ThreadRAII& oprator=(ThreadRAII&&) = default; std::thread& get() { return t; } private: DtorAction action; std::thread t; }; • ユーザ定義デストラクタがあるので、moveコンストラ クタが自動生成されなくなる。 • moveできなくする理由はないので作っておきましょう。
Item 37: Make std::threads unjoinable on all paths. std::threads をすべてのパスで unjoinable にしよう 覚えておくべきこと • std::thread をすべての系で unjoinable にする • 「破棄時に join 戦略」はデバッグしにくい性能上の 問題の原因となる • 「破棄時に detach 戦略」はデバッグしにくい未定義 動作を引き起こす • std::thread はデータメンバの最後で宣言しよう 2015年8月28日出版予定