マルチスレッドのプログラミング

ロック階層の使用例

同時に 2 つのリソースをアクセスすることがあります。一方のリソースを使用しているとき、もう一方のリソースも必要となる場合があります。2 つのスレッドが同じ 2 つのリソースを要求しようとして、対応する相互排他ロックを異なる順序で獲得しようとすると、問題が発生します。たとえば、2 つのスレッドがそれぞれ mutex の 1 と 2 をロックし、次に各スレッドが互いにもう一方の mutex をロックしようとすると、デッドロックが発生します。例 4–2 に、デッドロックが発生する場合のシナリオを示します。


例 4–2 デッドロック

スレッド 1 

スレッド 2 

pthread_mutex_lock(&m1);

/* use resource 1 */


pthread_mutex_lock(&m2);


/* use resources 1 and 2 */


pthread_mutex_unlock(&m2);


pthread_mutex_unlock(&m1);
pthread_mutex_lock(&m2);

/* use resource 2 */


pthread_mutex_lock(&m1);


/* use resources 1 and 2 */


pthread_mutex_unlock(&m1);


pthread_mutex_unlock(&m2);


この問題を回避する最善の方法は、スレッドで複数の mutex をロックする場合、常に同じ順序でロックすることです。ロックが常に規定された順序で実行されれば、デッドロックは起こらないはずです。この方法を「ロック階層」と呼びます。この方法では、mutex に論理的な番号を割り振って、mutex に順序を付けます。

自分が持つ mutex の番号より小さい番号が割り振られている mutex はロックできないという規定を守るようにします。

ただし、この方法が使用できない場合もあります。規定と違う順序で相互排他ロックを獲得しなければならないこともあるからです。そのような状況でデッドロックを防ぐには、pthread_mutex_trylock() を使用します。デッドロックが避けられないような事態が生じた場合は、あるスレッドが現在保持している mutex のロックを解除する必要があります。


例 4–3 条件付きロック

スレッド 1 

スレッド 2 

pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);

 

 

 

 

/* 解放 */ 

 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

for (; ;)

{ pthread_mutex_lock(&m2);

 

 

if(pthread_mutex_trylock(&m1)==0)

/* 獲得成功 */ 

break;

/* 獲得失敗 */ 

pthread_mutex_unlock(&m2);

}

/* ロックを獲得し、解放 */ 

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


例 4–3 では、スレッド 1 は mutex を規定通りの順序でロックしようとしていますが、スレッド 2 ではロックの順序が違います。デッドロックが発生しないようにするために、スレッド 2 は mutex 1 を慎重にロックしなければなりません。これは、スレッド 2 が mutex 1 が解放されるまで待つとすると、スレッド 1 との間にデッドロックの関係が生じる恐れがあるからです。

デッドロックの発生を防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex が使用可能な状態であれば獲得します。mutex が使用可能な状態でない場合、スレッド 2 はただちに終了し、エラーを返します。その時点で、スレッド 2 は mutex 2 を解放しなければなりません。その結果、スレッド 1 は mutex 2 をロックでき、その後 mutex 1 と mutex 2 の両方を解放します。