ミューテックスのリスク

ミューテックスの使用法は非常に単純に思えます。コードには、任意の時点で 1 つのスレッドのみがアクセスできるクリティカル セクションがあります。これはミューテックス m によって保証されます。 m.lock() と m.unlock() の呼び出しは、この排他性を保証します。しかし、悪魔は細部に宿ります。

デッドロック

デッドロックのさまざまな名前は恐ろしいものです。それらを致命的な抱擁(死の抱擁:-)と呼ぶ人もいますか?または死のキス。でも待ってください、デッドロックとは何ですか?

デッドロック
デッドロックとは、少なくとも 2 つのスレッドがブロックされている状態です。これは、各スレッドが、自分のリソースを解放する前に、他のスレッドが使用するリソースの解放を待っているためです。

デッドロックの結果は、完全な停止です。スレッドと通常はプログラム全体が永久にブロックされます .デッドロックが発生しやすい。興味がありますか?

例外と不明なコード

std::mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();

関数 getVar() 内の不明なコードが例外をスローした場合、 m.unlock() は呼び出されません。ミューテックス m を求める試みはすべて失敗し、プログラムはブロックされます。永遠に。しかし、そのコードの問題はそれだけではありません。 m.lock() がアクティブなときに、いくつかの (私たちには知られていない) 関数 get.Var() を呼び出します。関数 getVar() が同じロックを取得しようとするとどうなりますか?もちろん、あなたはそれを知っています。デッドロック。

より視覚的な例が必要ですか?

異なる順序でミューテックスをロックする

スレッド 1 とスレッド 2 は、作業を完了するために 2 つのリソースにアクセスする必要があります。残念ながら、彼らは異なる順序で 2 つのミューテックスによって保護されているリソースを要求します。この場合、スレッドの実行は、スレッド 1 がミューテックス 1 を取得し、次にスレッド 2 がミューテックス 2 を取得するようにインターリーブし、停止します。各スレッドは、他のスレッドのミューテックスを取得したいと考えています。このために、スレッドはリソースの解放を待つ必要があります。

コードで絵を表現するのは簡単です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// deadlock.cpp

#include <iostream>
#include <chrono>
#include <mutex>
#include <thread>

struct CriticalData{
 std::mutex mut;
};

void deadLock(CriticalData& a, CriticalData& b){

 a.mut.lock();
 std::cout << "get the first mutex" << std::endl;
 std::this_thread::sleep_for(std::chrono::milliseconds(1));
 b.mut.lock();
 std::cout << "get the second mutex" << std::endl;
 // do something with a and b
 a.mut.unlock();
 b.mut.unlock();
 
}

int main(){

 CriticalData c1;
 CriticalData c2;

 std::thread t1([&]{deadLock(c1,c2);});
 std::thread t2([&]{deadLock(c2,c1);});

 t1.join();
 t2.join();

}

スレッド t1 とスレッド t2 がデッドロック関数を呼び出します (12 ~ 20 行目)。デッドロックを処理するには、両方の関数で CriticalData c1 と c2 が必要です (27 行目と 28 行目)。オブジェクト c1 と c2 は共有アクセスから保護する必要があるため、ミューテックスを持っています (この例のコードを短くシンプルにするために、CriticalData にはミューテックス以外のメソッドやメンバーはありません)

16 行目で約 1 ミリ秒しかスリープしないため、デッドロックが発生します。

現在唯一の選択肢は、CTRL+C を押してプロセスを強制終了することです。

次は?

正直なところ、この例はマルチスレッド プログラムを作成する自信を高めるものではありません。さらに、新しいミューテックスごとに複雑さが 2 の累乗に増加します。ロックはミューテックスを安全な方法でカプセル化するため、この問題の解決策はロックです。どのように?こちらをご覧ください。 (校正者アレクセイ エリマノフ )