effective modern c++ 勉強会#3 item16

61
Effective Modern C++ 勉強会#3 Item 16 刈谷 満(@kariya_mitsuru) 2015/4/22

Upload: mitsuru-kariya

Post on 23-Feb-2017

301 views

Category:

Technology


2 download

TRANSCRIPT

Effective Modern C++勉強会#3Item 16

刈谷 満(@kariya_mitsuru)

2015/4/22

Item 16: Make const member functions thread safe.

const メンバ関数を

スレッドセーフにしよう

Item 16: Make const member functions thread safe.

多項式を表すクラス

⇒Polynomial

その多項式の根を計算するメンバ関数

⇒roots

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.

解決策

mutex を使え

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.

でも…

実はもっとヤバい…

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.えーマジ

マルチスレッド

未対応⁉

マルチスレッド

未対応が許されるのは

小学生までだよねー

©キモイガールズ

Item 16: Make const member functions thread safe.

覚えておくこと•並行コンテキストで決して使用しないと確信できない限り、const メンバ関数をスレッドセーフにしておくこと。

• std::atomic の使用は mutex よりも良いパフォーマンスを示す

かもしれないが、それは単一の変数やメモリ位置への操作のみに適している。