スレッド同期と競合状態
共有データはなぜ実行するたびに結果が異なりますか?
本ブログ作成文は、講義を目指して製作されました。 また、作成された文章の情報も間違っている可能性がありますので、ご注意ください。
同じプロセスのスレッドはヒープとグローバルデータを共有します。 おかげでデータをすばやく送受信できますが、同じ値を同時に読み書きすると、実行するたびに結果が変わることがあります。
この問題をRace Condition(競争状態)と呼ぶ。
count++は1回の作業ではありません
int count = 0;
void AddCount()
{
++count;
}
コードでは1行ですが、CPUの立場では値を読み込み、1を加えて、再度保存するさまざまなステップになることができます。
Thread Aがcount 0を読む
Thread Bがcount 0を読む
Thread Aが1を保存する
Thread Bが1を保存
2回増加させたが、最終結果は2ではなく1になる。 スレッドの実行順序はオペレーティングシステムの状況に依存するため、テスト時に時々現れるバグになることがあります。
Mutexで一度に1スレッドしか入らないようにする
std::mutex countMutex;
int count = 0;
void AddCount()
{
std::lock_guard<std::mutex> lock(countMutex);
++count;
}
Mutex(ミューテックス)をロックしたスレッドがクリティカルゾーンを実行している間、他のスレッドは待機します。 lock_guardを使用すると、関数が終了した場合や例外が発生しても自動的にロックを解除します。
Atomicは単純な値に使用できます
1つの整数を増やすように、小さな演算ではAtomic(原子演算)を使用できます。
std::atomic<int> count = 0;
void AddCount()
{
++count;
}
しかし、複数の変数の関係を一度に安全に維持しなければならないなら、atomic 1つでは解決されない。 たとえば、金の差し引きとアイテムの追加が必ず成功する必要がある場合は、2つを1つの臨界ゾーンで保護する必要があります。
Deadlockも注意しなければならない
2つのスレッドが互いに相手が持っているロックを待つと誰も進めない。 これをDeadlock(デッドロック)という。
スレッドA:ロック1をつかみ、ロック2を待つ
スレッドB:ロック2をつかみ、ロック1を待つ
ロックを常に同じ順序で獲得し、ロックをとったまま長くかかる作業をしない習慣が必要だ。
マルチスレッドの鍵は、スレッドをたくさん作ることではありません。 共有データを減らし、必ず共有しなければならない範囲を明確に定め、その範囲に合った同期ツールを使うことである。