Más contenido relacionado
La actualidad más candente (20)
Similar a Effective modern c++ 8 (7)
Effective modern c++ 8
- 2. アジェンダ
• Item 37: Make std::threads unjoinable on all
paths.
std::threads をすべてのパスで unjoinable にしよう
- 3. Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
• 「joinable な std::thread」は裏にあるスレッド
が実行中か実行可能な状態になっているスレッドに対
応する
• ブロック中かスケジュールを待っている状態のスレッド
• まさに実行中のスレッド
• 「unjoinable な std::thread」は joinable でな
いスレッドのこと
すべての std::thread は
joinable / unjoinable のどちらかの状態
- 4. Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
• デフォルトコンストラクタで生成した std::thread
• 何も実行するものがなく裏のスレッドに紐づかない
• 他のオブジェクトに move 済みの std::thread
• move の結果、裏のスレッドが他の std::thread に移る
• join 済みの std::thread
• join すると裏のスレッドも実行を停止し、切り離される
• detach 済みの std::thread
• detach すると裏のスレッドとの接続が切れる
unjoinable になるパターン
- 5. Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
なぜ join 可能かどうかが重要なのか
→ joinable な std::thread がデストラクトされると
プログラムの実行が終了してしまうから。
- 6. 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;
}
- 7. 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;
}
重い処理
→並列実行
- 8. 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;
}
joinable な t がデスト
ラクトされる系がある
- 9. Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
joinable な std::thread がデストラクトされると
プログラムの実行が終了してしまうから。
→ デストラクタで対策すれば良いのでは?
• 暗黙に join してあげれば良さそう
• しかし追跡しずらい性能上の問題が出るかも。例えば
conditionsAreSatisfied() が既に false を返したのに
フィルタが終わるのを待ち続けるとか。
• 暗黙に detach してあげれば良い?
• 裏で動くスレッドが切り離された後も実行を続けるので、いろい
ろ良くないことが起きる。
• 例えば・・・(次ページ)
- 10. 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 があった領域)まで到達すると、
スタック領域が自然に書き換わるように見える
これをデバッグする楽しさといったら無いよ!
- 11. 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 クラスは無い…
- 12. 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;
};
- 13. 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;
};
引数の順序は thread が先だが
宣言の順序は thread が後
- 14. 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;
};
引数の順序は thread が先だが
宣言の順序は thread が後
thread は初期化されるとすぐ動き
出すので、クラスの一番後ろで宣言
するのは良い習慣。
スレッドオブジェクトが構築された
ときには、他のデータメンバが初期
化されているのを保証できる。
- 15. 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 がデストラクトされるときには、他のスレッ
ドはメンバ関数を呼び出せないはずである。
マルチスレッドで
競合しそう…
- 16. 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;
}
最初のコード
- 17. 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 を使ったコード
- 18. 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できなくする理由はないので作っておきましょう。
- 19. • std::thread をすべての系で unjoinable にする
• 「破棄時に join 戦略」はデバッグしにくい性能上の
問題の原因となる
• 「破棄時に detach 戦略」はデバッグしにくい未定義
動作を引き起こす
• std::thread はデータメンバの最後で宣言しよう
覚えておくべきこと
Item 37: Make std::threads unjoinable on all paths.
std::threads をすべてのパスで unjoinable にしよう
2015年8月28日出版予定