表 4-3 に、この章で説明する mutex ロック操作関数を示します。
表 4-3 相互排他ロック操作ルーチン
操作 |
参照先 |
---|---|
mutex の初期化 | |
mutex のロック | |
mutex のロック解除 | |
ブロックしないで行う mutex のロック | |
mutex の削除 |
デフォルトスケジューリング方針 SCHED_OTHER は、スレッドによるロックの獲得順序を指定していません。複数のスレッドが mutex を待っているときの獲得の順序は不定です。競合するときは、スレッドを優先順位でブロック解除するというのがデフォルト動作です。
pthread_mutex_init(3T) は、mp が指す mutex をデフォルト値に初期化 (mattr が NULL の場合) するか、pthread_mutexattr_init() ですでに設定されている mutex 属性を指定するときに使用します。(Solaris スレッドについては、「mutex_init(3T)」を参照)。
プロトタイプ: int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr); #include <pthread.h> pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t mattr; int ret; /* mutex をデフォルト値に初期化する */ ret = pthread_mutex_init(&mp, NULL); /* mutex を初期化する */ ret = pthread_mutex_init(&mp, &mattr); |
初期化された mutex は、ロック解除状態になります。mutex は、プロセス間で共有されているメモリー内または個々のプロセス専用のメモリー内に置かれます。
mutex メモリーは、初期化する前にクリアしてゼロにする必要があります。
mattr を NULL にするのは、デフォルト mutex 属性オブジェクトのアドレスを渡すのと同じことですが、メモリーのオーバーヘッドがありません。
mutex を静的に定義する場合、マクロ PTHREAD_MUTEX_INITIALIZER により、デフォルト属性を持つように直接初期化できます。
mutex ロックは、他のスレッドが使用している可能性がある間は再初期化したり削除したりしてはいけません。どちらの動作も正しく行われなければプログラムで障害が発生します。mutex を再初期化または削除する場合、アプリケーションがその mutex を使用していないことが確実でなければなりません。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を戻します。
#include <pthread.h> int pthread_mutex_consistent_np(pthread_mutex_t *mutex); |
pthread_mutex_consistent_np() が適用されるのは、シンボル _POSIX_THREAD_PRIO_INHERIT が定義され、かつプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されている mutex に対してのみです。
mutex の所有者が終了すると、mutex が不整合になる可能性があります。
pthread_mutex_consistent_np が、mutex の所有者の終了後に mutex オブジェクト、mutex を整合させます。
不整合の mutex を獲得するには、pthread_mutex_lock() を呼び出します。戻り値 EOWNERDEAD は不整合な mutex であることを示します。
pthread_mutex_consistent_np() は、pthread_mutex_lock() への前の呼び出しによって獲得された mutex を保持している間に呼び出してください。
mutex によって保護されている重要領域が、終了した所有者によって不整合の状態のままになっている可能性があるので、mutex によって保護されている重要領域を整合させることができる場合にのみ mutex を整合させてください。
整合された mutex に対して pthread_mutex_lock()、pthread_mutex_unlock() および pthread_mutex_trylock() を呼び出すと、通常の方法で動作します。
不整合でない、あるいは保持されていない mutex に対する pthread_mutex_consistent_np() の動作は、定義されていません。
pthread_mutex_consistent_np() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示しています。次の条件が検出されると、関数は失敗し、対応する値を返します。
次の条件が検出されると、pthread_mutex_consistent_np() は失敗します。
オプション _POSIX_THREAD_PRIO_INHERIT が定義されていないか、あるいはこの実装が pthread_mutex_consistent_np() 関数をサポートしていません。
次の条件が検出されると、pthread_mutex_consistent_np() は失敗します。
プロトタイプ: int pthread_mutex_lock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_ mutex_lock(&mp); /* mutex を獲得する */ |
pthread_mutex_lock(3T) は、mutex が指す mutex をロックします。pthread_mutex_lock() が戻ると、呼び出しスレッドが mutex をロックした状態になっています。mutex が別のスレッドによってすでにロックされている (所有されている) 場合は、呼び出しスレッドは mutex が使用可能になるまでブロックされます (Solaris スレッドについては、「mutex_lock(3T)」を参照)。
mutex 型が PTHREAD_MUTEX_NORMAL の場合、デッドロックの検出は行われません。mutex をもう一度ロックしようとするとデッドロックが発生します。スレッドが、ロックされていない mutex やロック解除された mutex をロック解除しようとした場合、引き起こされる動作は未定義です。
mutex 型が PTHREAD_MUTEX_ERRORCHECK の場合は、エラーチェックが提供されます。すでにロックされた mutex をもう一度ロックしようとすると、エラーが返されます。ロックされていない mutex やロック解除された mutex をロック解除しようとすると、エラーが返されます。
mutex 型が PTHREAD_MUTEX_RECURSIVE の場合は、mutex はロックの回数を記録します。スレッドが最初に正常に mutex を獲得すると、ロック計数は 1 に設定されます。この mutex をスレッドがさらにロックするたびに、ロックカウントが 1 ずつ増えます。スレッドが mutex をロック解除するたびに、ロックカウントが 1 ずつ減ります。ロックカウントが 0 になると、その mutex を別のスレッドが獲得できるようになります。ロックされていない mutex やロック解除された mutex をロック解除しようとすると、エラーが返されます。
mutex 型が PTHREAD_MUTEX_DEFAULT の場合、繰り返し mutex をロックしようとすると、引き起こされる動作は未定義です。 mutex をロックしていないスレッドがロック解除しようとした場合、引き起こされる動作は未定義です。また、ロックされていない mutex をロック解除しようとした場合、引き起こされる動作は未定義です。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、次の値を戻します。
シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されており、pthread_mutexattr_setrobust_np() の robustness 引数が PTHREAD_MUTEX_ROBUST_NP である場合、この関数は失敗し、次の値を返します。
この mutex の前の所有者が mutex を保持している間に終了しました。現在この mutex は、呼び出し元によって所有されています。呼び出し元は、mutex によって保護された状態を整合させるよう試行する必要があります。
呼び出し元が状態を整合させることができた場合、その mutex に対して pthread_mutex_consistent_np() を呼び出して、mutex をロック解除します。これ以降の pthread_mutex_lock() の呼び出しは正常に動作します。
呼び出し元が状態を整合させることができない場合は、その mutex に対して pthread_mutex_init() は呼び出さず、mutex をロック解除します。これ以降の pthread_mutex_lock() のすべての呼び出しは mutex の獲得に失敗し、エラーコード ENOTRECOVERABLE を返します。
EOWNERDEAD を持つロックを獲得した所有者が終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。
獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。
同時に保持される mutex の上限数を超えています。
pthread_mutex_unlock(3T) は、mutex が指す mutex のロックを解除します。(Solaris スレッドについては、「mutex_unlock(3T)」を参照)。
プロトタイプ: int pthread_mutex_unlock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_unlock(&mutex); /* mutex を解除する */ |
pthread_mutex_unlock() は、mutex が指す mutex オブジェクトを解放します。mutex を解放する方法は、mutex の 型属性に依存します。pthread_mutex_unlock() が呼び出されたときに、指定された mutex が指す mutex オブジェクトでブロックされているスレッドがあり、この呼び出しによって mutex が使用できるようになると、スケジューリング方針に基づいて mutex を獲得するスレッドが決定されます。PTHREAD_MUTEX_RECURSIVE のタイプの mutex の場合、mutex が使用可能になるのは、カウントが 0 になり、pthread_mutex_unlock() を呼び出したスレッドがこの mutex のロックを解除したときです。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。
pthread_mutex_trylock(3T) は、mutex が指す mutex のロックを試みます。(Solaris スレッドについては、「mutex_trylock(3T)」を参照)。
プロトタイプ: int pthread_mutex_trylock(pthread_mutex_t *mutex); #include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_trylock(&mutex); /* mutex のロックを試みる */ |
この関数はブロックしない点を除いて、pthread_mutex_lock() と同じ働きをします。mutex が参照している mutex オブジェクトが、現在のスレッドを含むいずれかのスレッドによってロックされている場合は、呼び出しはただちに返されます。mutex オブジェクトがロックされていなければ、呼び出しスレッドがロックを獲得します。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、次の値を戻します。
シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されており、pthread_mutexattr_setrobust_np() の robustness 引数が PTHREAD_MUTEX_ROBUST_NP である場合、この関数は失敗し、次の値を返します。
この mutex の前の所有者が mutex を保持している間に終了しました。現在この mutex は、呼び出し元によって所有されています。呼び出し元は、mutex によって保護された状態を整合させるよう試行する必要があります。
呼び出し元が状態を整合させることができた場合、その mutex に対して pthread_mutex_consistent_np() を呼び出して、mutex をロック解除します。これ以降の pthread_mutex_lock() の呼び出しは正常に動作します。
呼び出し元が状態を整合させることができない場合は、その mutex に対して pthread_mutex_init() は呼び出さず、mutex をロック解除します。これ以降の pthread_mutex_trylock() のすべての呼び出しは mutex の獲得に失敗し、エラーコード ENOTRECOVERABLE を返します。
EOWNERDEAD を持つロックを獲得した所有者が終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。
獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。
同時に保持される mutex の上限数を超えています。
pthread_mutex_destroy(3T) は、mp が指す mutex に関連するすべての状態を削除します。(Solaris スレッドについては、「mutex_destroy(3T)」を参照)。
プロトタイプ: int pthread_mutex_destroy(pthread_mutex_t *mp); #include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex を削除する */ |
mutex の記憶領域は解放されません。
正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
例 4-1 に、mutex ロックを示すコードの一部を示します。
#include <pthread.h> pthread_mutex_t count_mutex; long long count; void increment_count() { pthread_mutex_lock(&count_mutex); count = count + 1; pthread_mutex_unlock(&count_mutex); } long long get_count() { long long c; pthread_mutex_lock(&count_mutex); c = count; pthread_mutex_unlock(&count_mutex); return (c); } |
例 4-1 の 2 つの関数は、相互排他 (mutex) ロックをそれぞれ別の目的で使用しています。increment_count() 関数は、相互排他ロックによって共有変数の原子的操作による更新を保証しています。get_count() 関数は、相互排他ロックによって 64 ビット値の count が原子的に読み取られるようにしています。32 ビットアーキテクチャでは、long
long
は実際には 2 つの 32 ビット値として処理されます。
整数はほとんどのマシンで共通のワードサイズであるため、整数値の読み取りは原子操作です。
同時に 2 つのリソースをアクセスすることがあります。一方のリソースを使用しているとき、もう一方のリソースも必要となる場合があります。2 つのスレッドが同じ 2 つのリソースを要求しようとして両者が異なる順序で、対応する相互排他ロックを獲得しようとする場合に問題が生じることがあります。たとえば 、2 つのスレッドがそれぞれ mutex の 1 と 2 をロックした場合、次に各スレッドが互いにもう一方の mutex をロックしようとするとデッドロックが発生します。例 4-2 に、デッドロックが発生する場合のシナリオを示します。
スレッド 1 |
スレッド 2 |
---|---|
pthread_mutex_lock(&m1);
/* リソース 1 を使用 */
pthread_mutex_lock(&m2);
/* リソース 1 と 2 を使用 */ pthread_mutex_unlock(&m2); pthread_mutex_unlock(&m1); |
pthread_mutex_lock(&m2);
/* リソース 2 を使用 */
pthread_mutex_lock(&m1);
/* リソース 1 と 2 を使用 */
pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); |
この問題を回避する最善の方法は、スレッドで複数の mutex をロックする場合、常に同じ順序でロックすることです。この方法をロック序列と呼び、mutex に論理的な番号を割り振ることにより mutex に順序を付けます。
自分がある番号を持つ mutex を保持しているときより小さい番号が割り振られている mutex はロックできないという規定を守るようにします。
ロック lint ツールを使うと、この例で示したようなデッドロックの問題を検出できます。この種のデッドロック問題を回避する最善の方法は、ロック階層を使用することです。常に一定の順序でロックする限り、デッドロックは発生しません。
ただし、この方法は常に使用できるとは限りません。規定と違う順序で相互排他ロックを獲得しなければならないこともあるからです。そのような状況でデッドロックを防ぐには、pthread_mutex_trylock() を使用します。デッドロックが避けられないような事態が生じた場合は、ある 1 つのスレッドが現在保持している mutex のロックを解除する必要があります。
スレッド 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 を慎重にロックしなければなりません。これは、mutex の 1 が解放されるまで待つとすると、スレッド 1 との間にデッドロックの関係が生じる恐れがあるからです。
これを防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex がロックされていなければロックします。ロックされていれば、スレッド 2 はただちにエラーを返します。その時点で、スレッド 2 は mutex の 2 を解放しなければなりません。その結果、スレッド 1 は mutex の 2 をロックでき、最終的には mutex の 1 と 2 の両方を解放します。
例 4-4 と例 4-5で、一度に 3 つのロックを獲得する場合を説明します。この例では、デッドロックを防ぐために規定された順序でロックします。
typedef struct node1 { int value; struct node1 *link; pthread_mutex_t lock; } node1_t; node1_t ListHead; |
この例で使用する片方向リンクのリスト構造体は、各ノードに相互排他ロックを含んでいます。このリストから特定のノードを削除する場合は、最初に ListHead (これが削除されることはない) の位置からリストをたどって目的のノードを探します。
この検索を同時並行的に行われる削除から保護するために、各ノードをロックしてからノードの内容にアクセスしなければなりません。すべての検索が ListHead の位置から開始されるので、常にリストの順序でロックされます。このため、デッドロックは決して発生しません。
目的のノードが見つかった時は、この変更がそのノードと直前のノードの両方に影響を与えるため、両方をロックします。直前のノードのロックが常に最初に獲得されるので、ここでもデッドロックの心配はありません。例 4-5 は、片方向リンクリストから特定のノードを削除する C コードを示しています。
例 4-6 は、前述のリスト構造を修正して循環リストにしたものです。先頭のノードとして識別されるノードはありません。スレッドは適当な 1 つのノードに関連付けられると、そのノードと次のノードに対して操作を行います。この状況ではロック序列は適用できません。明らかに階層 (つまり、リンクをたどる順番) が循環的だからです。
typedef struct node2 { int value; struct node2 *link; pthread_mutex_t lock; } node2_t; |
例 4-7 では 2 つのノードをロックし、両方のノードに対してある操作を行なっている C コードを示します。