effective modern c++ 勉強会#3 item16
TRANSCRIPT
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType =
std::vector<double>;
RootsType roots() const;
…
};
多項式の根を計算するメンバ関数は、多項式自体を変更しない↓
const メンバ関数
根を保持するデータ構造(“using” については Item 9 を参照)
Item 16: Make const member functions thread safe.
多項式の根の計算は高くつく。
⇓
必要なければ計算したくない。
でも 2 回以上は計算したくない。
⇓
最初に使われた時に計算しよう!
計算した根はキャッシュしよう!
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
bool rootsAreValid{ false };
RootsType rootVals{};
};
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
bool rootsAreValid{ false };
RootsType rootVals{};
};
キャッシュが有効じゃなかったら…
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
bool rootsAreValid{ false };
RootsType rootVals{};
};
キャッシュが有効じゃなかったら…
根を計算してそれを rootVals に保存し…
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
bool rootsAreValid{ false };
RootsType rootVals{};
};
キャッシュが有効じゃなかったら…
根を計算してそれを rootVals に保存し…
キャッシュを有効にする!
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
bool rootsAreValid{ false };
RootsType rootVals{};
};
キャッシュが有効じゃなかったら…
根を計算してそれを rootVals に保存し…
キャッシュを有効にする!あれ?const メンバ関数じゃなかったっけ?
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
mutable!roots は概念的にはオブジェクトを変更しない(論理的 const性)
詳しくはWebで!!Effective C++ 第3版、Item 3 参照
Item 16: Make const member functions thread safe.
ところで、この Polynomial クラスのオブジェクトを複数スレッドで同時に使うと…
Polynomial p;
…
スレッド2
auto valsGivingZero = p.roots();
スレッド1
auto rootsOfP = p.roots();
const メンバ関数だから読むだけだし、同期しなくても大丈夫っしょ!!
Item 16: Make const member functions thread safe.
ところで、この Polynomial クラスのオブジェクトを複数スレッドで同時に使うと…
Polynomial p;
…
スレッド2
auto valsGivingZero = p.roots();
スレッド1
auto rootsOfP = p.roots();
const メンバ関数だから読むだけだし、同期しなくても大丈夫っしょ!!
死
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
②rootsAreValidを読む⇓
まだ false!
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
③計算しては書き込む!(まだ false)
②rootsAreValidを読む⇓
まだ false!
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
③計算しては書き込む!(まだ false)
②rootsAreValidを読む⇓
まだ false!
④計算しては書き込む!(まだ false)
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
③計算しては書き込む!(まだ false)
②rootsAreValidを読む⇓
まだ false!
④計算しては書き込む!(まだ false)
データレース!
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
if (!rootsAreValid) {
…
rootsAreValid = true;
}
return rootVals;
}
private:
mutable bool rootsAreValid{ false };
mutable RootsType rootVals{};
};
スレッド2スレッド1
①rootsAreValidを読む⇓
まだ false
③計算しては書き込む!(まだ false)
②rootsAreValidを読む⇓
まだ false!
④計算しては書き込む!(まだ false)
データレース!ようこそUBへ!
Item 16: Make const member functions thread safe.
ここでの問題は…
roots は const メンバ関数なのに、スレッドセーフじゃない。
⇓
const メンバ関数であることは C++11 でも C++98 でも正しい。
(rootsは多項式の値を変更しない)
⇓
直すべきはスレッドセーフ性の欠如
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
std::lock_guard<std::mutex> g(m);
ここは前と一緒…
return rootVals;
}
private:
mutable std::mutex m;
ここも前と一緒…
};
lock_guardを使ってm をロック!
ブロックの終わりでm をアンロック!
ここのmutable にも注意!ロック、アンロックは const メンバ関数じゃない
でも、m が変わっても多項式はやっぱり変わらない!
Item 16: Make const member functions thread safe.
Caution!(Errataあり)
std::mutex はコピーもムーブもできない。
Polynomial に m を追加した結果、
コピーもムーブも出来なくなった。
Item 16: Make const member functions thread safe.
Caution!(Errataあり)
std::mutex はコピーもムーブもできない。
Polynomial に m を追加した結果、
コピーもムーブも出来なくなった。
独自のコピー、ムーブコンストラクタ、代入演算子を定義すれば
いいと思うんだけど…
Item 16: Make const member functions thread safe.
コメントあり
もし Polynomial が変更不可だったら、std::call_once とstd::once_flag を使う方がいんじゃね?
⇓
ちょっと書いてみました
Item 16: Make const member functions thread safe.class Polynomial {
public:
using RootsType = std::vector<double>;
RootsType roots() const {
std::call_once(flag, [this]{ calc(); });
return rootVals;
}
void calc() const {
…
}
private:
mutable std::once_flag flag{};
mutable RootsType rootVals{};
};
call_once を使ってcalc を一回だけ呼ぶ(λ をつかってる理由は
Item 34 参照)
実際に計算、格納するメンバ関数もちろん const メンバ関数
もちろん once_flagもmutable!
Item 16: Make const member functions thread safe.
場合によっては mutex は重過ぎ
例)メンバ関数が呼ばれた回数を数えたい
⇓
std::atomic で十分
(実際に std::atomic の方が mutex より軽いかどうかは、動作するハードウェアと標準ライブラリの実装によるので注意!)
Item 16: Make const member functions thread safe.class Point {
public:
…
double distanceFromOrigin() const noexcept
{
++callCount;
return std::hypot(x, y);
}
private:
mutable std::atomic<unsigned> callCount{ 0 };
double x, y;
};
アトミック(不可分)なインクリメント(Item 40 参照)
Item 16: Make const member functions thread safe.class Point {
public:
…
double distanceFromOrigin() const noexcept
{
++callCount;
return std::hypot(x, y);
}
private:
mutable std::atomic<unsigned> callCount{ 0 };
double x, y;
};
例によって、std::atomic<T> もコピーはおろかムーブも出来ないので注意!(本書Errata)
Item 16: Make const member functions thread safe.class Point { // 二次元の点
public:
…
double distanceFromOrigin() const noexcept // noexcept については
{
++callCount;
return std::hypot(x, y);
}
private:
mutable std::atomic<unsigned> callCount{ 0 };
double x, y;
};
あ、あと、std::hypotもわりとデキる奴なので
忘れないであげてください!(><)(本書Errata)
hypotenuse squared
Item 16: Make const member functions thread safe.
でも…
mutex より std::atomic の方が軽いからと言って、使いすぎは禁物!
例)重い計算結果のキャッシュ(int)とキャッシュの有効フラグ(bool)
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
キャッシュする値とその有効フラグを
atomic にしました!
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
キャッシュする値とその有効フラグを
atomic にしました!
ホントに大丈夫…?
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
②計算して格納(まだ無効)
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
②計算して格納(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
②計算して格納(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
④計算して格納(まだ無効)
Item 16: Make const member functions thread safe.
class Widget {public:
…int magicValue() const {if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}
private:mutable std::atomic<bool> cacheValid{ false };mutable std::atomic<int> cachedValue;
};
スレッド2スレッド1
②計算して格納(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
④計算して格納(まだ無効)残念!!!
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
じゃあじゃあ、キャッシュの有効化と値の代入を逆にしよう!
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
じゃあじゃあ、キャッシュの有効化と値の代入を逆にしよう!
ホントにホントに大丈夫…?
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
④2つとも計算する(まだ無効)
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効か確認する⇓
まだ無効
①有効か確認する⇓
まだ無効
④2つとも計算する(まだ無効)
残念!!!(本書Errata)
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効にする!(まだ格納前!)
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効にする!(まだ格納前!)
④有効か確認する⇓
有効!?値を返す!!!
①有効か確認する⇓
まだ無効
Item 16: Make const member functions thread safe.
class Widget {
public:
…
int magicValue() const {
if (cacheValid) return cachedValue;
else {
auto val1 = expensiveComputation1();
auto val2 = expensiveComputation2();
cacheValid = true;
return cachedValue = val1 + val2;
}
}
…
};
スレッド2スレッド1
②2つとも計算する(まだ無効)
③有効にする!(まだ格納前!)
④有効か確認する⇓
有効!?値を返す!!!
①有効か確認する⇓
まだ無効
死
Item 16: Make const member functions thread safe.
教訓
単一の変数やメモリ位置の同期が必要
⇒ std::atomic
2 つ以上の変数やメモリ位置に対して不可分操作が必要
⇒ mutex(std::atomic じゃNG!)
Item 16: Make const member functions thread safe.class Widget {public:…int magicValue() const {std::lock_guard<std::mutex> guard(m);if (cacheValid) return cachedValue;else {
auto val1 = expensiveComputation1();auto val2 = expensiveComputation2();cachedValue = val1 + val2;cacheValid = true;return cachedValue;
}}…
private:mutable std::mutex m;mutable int cachedValue;mutable bool cacheValid{ false };
};
lock_guardを使ってm をロック!
ブロックの終わりでm をアンロック!
例によってmutable!
もはや atomic の必要なし!
Item 16: Make const member functions thread safe.
ところで…
散々マルチスレッドの話をしてきたけど、シングルスレッド前提の場合は?
⇓これらの対応は不要
(その場合、mutex や std::atomic に関するコストを避けられる)
Item 16: Make const member functions thread safe.
ところで…
散々マルチスレッドの話をしてきたけど、シングルスレッド前提の場合は?
⇓これらの対応は不要
(その場合、mutex や std::atomic に関するコストを避けられる)
Item 16: Make const member functions thread safe.えーマジ
マルチスレッド
未対応⁉
マルチスレッド
未対応が許されるのは
小学生までだよねー
©キモイガールズ