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

デッドロックの回避

ある一組のスレッドが一連のリソースの獲得で競合したまま、永久にブロックされた状態に陥っているとき、その状態をデッドロックと呼びます。実行可能なスレッドがあるからといって、デッドロックが発生していないという証拠にはなりません。

代表的なデッドロックは、「自己デッドロック」(「再帰的なデッドロック」) です。自己デッドロック (再帰的なデッドロック) は、すでに保持しているロックをスレッドがもう一度獲得しようとしたとき発生します。これは、ちょっとしたミスで簡単に発生してしまいます。

たとえば、一連のモジュール関数で構成されるコードモニターについて考えてみましょう。このモジュール関数は、呼び出しの間 mutex ロックを保持します。この場合、このモジュール内の相互排他ロックの保護下にある関数間で呼び出しが行われると、たちまちデッドロックが発生します。ある関数がこのモジュールの外部のコードを呼び出し、そこから再び同じ相互排他ロックで保護されているモジュールを呼び出した場合にもデッドロックが発生します。

この種のデッドロックを回避するには、モジュールの外部の関数が、元のモジュールに依存している可能性がある場合、この関数を呼び出さないようにします。特に、不変式を再設定しないで再び元のモジュールを呼び出すような関数を呼び出さないことです。さらに、呼び出しの前にすべてのモジュールロックを解除するようにします。次に、その呼び出しを終了してもう一度ロックを獲得した後、所定の状態を評価して、意図している操作がまだ有効であるか確認します。

もう 1 つ別の種類のデッドロックがあります。スレッド 1、2 がそれぞれ mutex A、B のロックを獲得しているものと仮定します。スレッド 1 が mutex B を、スレッド 2 が mutex A をロックしようとすると、スレッド 1 は mutex B を待ったままブロックされ、スレッド 2 は mutex A を待ったままブロックされます。何も変化しない状態になります。つまり、これらのスレッドが永久にブロックされ、デッドロック状態となります。

この種のデッドロックを回避するには、ロックを獲得する順序、「ロック階層」を設定します。すべてのスレッドが常に一定の順序でロックを行う限り、この種のデッドロックは生じません。

しかし、ロックを行う順序を一定に保つという規則を守っていればよいとは必ずしも言えません。たとえば、スレッド 2 が mutex B を保持している間に、モジュールの状態に関して多くの仮定条件を立てた場合、次に mutex A のロックを獲得するために mutex B のロックを解除し、mutex A のロックを獲得したあとにもう一度 mutex B のロックを獲得すると、先の仮定が無効になります。モジュールの状態をもう一度評価しなければならなくなります。

通常、ブロックを行う同期プリミティブには、ロックを獲得しようとしてできなかった場合にエラーとなるプリミティブが用意されています。一例として、pthread_mutex_trylock() があります。このプリミティブの動作によって、競合が発生していないときには、スレッドによるロック階層違反が許可されます。競合があるときは、通常は保持しているロックをいったん解除してから、順番にロックを実行しなければなりません。

スケジューリングに関するデッドロック

ロックが獲得される順序が保証されていないため、特定のスレッドがロックを獲得できないという問題が発生します。

通常、この問題は次のような状況で起こります。スレッドが、保持していたロックを解除し、少し時間をおいてからもう一度ロックを獲得するものとします。このとき、ロックはいったん解除されたので、ほかのスレッドがロックを獲得したように見えます。しかし、ロックを保持していたスレッドはブロックされません。その結果、このスレッドは、ロックを解除してからロックを再度獲得する間も引き続き実行されます。この場合、ほかのスレッドは実行されません。

通常、この種の問題を解決するには、ロックを再度獲得する呼び出しの直前に sched_yield(3C)() を呼び出します。sched_yield() 関数は、その他のスレッドに実行とロックの獲得を許可します。

必要なタイムスライスの大きさはアプリケーションに依存するため、システムでは特に制限していません。sched_yield() を呼び出して、スレッドが共有する時間を設定してください。

ロックに関する指針

ロックするときには、次の簡単な指針に従ってください。

デッドロックの検出

Sun Studio Thread Analyzer は、プログラム内のデッドロックを検出するために使用できるツールです。Thread Analyzer は、実際のデッドロックだけでなく、潜在的なデッドロックも検出できます。潜在的なデッドロックは、特定の実行で常に発生するわけではありませんが、スレッドのスケジューリングやスレッドによるロック要求のタイミングによっては、プログラムの任意の実行で発生する可能性があります。実際のデッドロックは、プログラムの実行中に発生して、関連するスレッドをハングアップさせますが、これによってプロセス全体をハングアップさせるかどうかは場合によって異なります。

『Sun Studio 12: スレッドアナライザユーザーズガイド』を参照してください。