前書き
C++のマルチスレッド処理を過去の資産を参考にして書いていたが、だんだんと自分の理解が怪しくなってきたので、いまさらながらC++11のスレッド処理を調査した。特に排他処理関係の備忘を載せておく。
マルチスレッド排他処理の基本
排他処理の基本はMutexを使う。Windows的にはクリティカルセクションとミューテックス
は別物で、使用目的や速度を考慮して使い分けるものである。しかし、C++11では基本的にクリティカルセクションの実現に std::mutex というものを使用する。どうやら内部的にはWinAPIのMutexではなく、別の同期処理を呼び出しているらしい。
あるスレッドが std::mutex インスタンスのlock関数を呼び出すと、同じスレッドでunlock関数を呼び出すまで、他のスレッドは同一インスタンスのlock関数を呼び出すことができなくなる。ただし、いちいちlock関数やunlock関数を呼ぶようなコードを書くと、途中でreturnなどが来た場合にunlockし忘れる可能性がある。
なので、基本的にはスコープドロックの仕組みを使って、以下のように書く。
std::mutex mtx;
void ClassA::funcA()
{
// 非同期処理
{
std::lock_guard lock(mtx);
// クリティカルセクション
}
// 非同期処理
}
これだけで、std::lock_guardクラスのコンストラクタとデストラクタが自動的にlock・unlockを呼び出してくれる。
注意
先ほどのコードは参考用なので簡略化しているが、ここでひとつ気を付けなければいけないことがある。mtx自体のスコープである。ClassAのメンバーなら、所属するClassAインスタンス内だけで排他処理を行い、インスタンス間では排他にならない。
class ClassA {
private:
std::mutex mtx;
public:
void funcA()
{
std::lock_guard lock(mtx);
// クリティカルセクション
}
};
int main()
{
ClassA ca;
// スレッドを4つ作る
std::vector ths(4);
for (auto& th : ths) {
th = std::thread([&ca] { ca.funcA(); });
}
// すべてのスレッドが終わるのを待つ
for (auto& th : ths) {
th.join();
}
return 0;
}
mtxがClassAのメンバーではなく、グローバルもしくはスタティックな変数であれば、全てのClassAインスタンス間で排他が行われる。
特にC++の場合、クラスを使わない関数プログラムが記述可能で、以下のような関数ベースのサンプルコードからクラスに適用する場合に、どういう目的で排他をするのか、気を付けなければいけない。
std::mutex mtx;
void funcA() {
std::lock_guard lock(mtx);
// クリティカルセクション
}
int main() {
// スレッドを4つ作る
std::vector ths(4);
for (auto& th : ths) {
th = std::thread(funcA);
}
// すべてのスレッドが終わるのを待つ
for (auto& th : ths) {
th.join();
}
return 0;
}