表 4–3 に、相互排他ロックを操作する関数を示します。
表 4–3 相互排他ロック操作ルーチン
操作 |
参照先 |
---|---|
mutex の初期化 | |
mutex の整合性保持 | |
mutex のロック | |
mutex のロック解除 | |
ブロックしないで行う mutex のロック | |
指定した時間までの mutex のロック | |
指定した時間間隔内の mutex のロック | |
mutex の削除 |
デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドによるロックの獲得順序を指定していません。複数の SCHED_OTHER スレッドが mutex を待っているときの獲得の順序は不定です。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、優先順位に従って待機スレッドをブロック解除します。
mp が指す mutex をデフォルト値に初期化する場合、またはすでに pthread_mutexattr_init() で設定されている mutex 属性を指定する場合は、pthread_mutex_init(3C) を使用します。mattr のデフォルト値は NULL です。
int pthread_mutex_init(pthread_mutex_t *restrict mp, const pthread_mutexattr_t *restrict mattr);
#include <pthread.h> pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t mattr; int ret; /* initialize a mutex to its default value */ ret = pthread_mutex_init(&mp, NULL); /* initialize a mutex */ ret = pthread_mutex_init(&mp, &mattr);
初期化された mutex は、ロック解除状態になります。mutex は、プロセス間で共有されているメモリー内または個々のプロセス専用のメモリー内に置かれます。
PTHREAD_MUTEX_ROBUST_NP 属性で初期化される mutex の場合は、初期化の前に mutex メモリーをゼロにクリアーする必要があります。
mattr を NULL にするのは、デフォルト mutex 属性オブジェクトのアドレスを渡すのと同じことですが、メモリーのオーバーヘッドがありません。
静的に定義された mutex をデフォルト値に初期化するには、マクロ PTHREAD_MUTEX_INITIALIZER を使用します。
ほかのスレッドによって使用されている mutex は、初期化したり削除したりできません。初期化や削除の動作が正常に行われないと、プログラムで障害が発生します。mutex を再初期化または削除する場合、アプリケーションがその mutex を使用していないことが確実でなければなりません。
pthread_mutex_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EBUSY
説明:mp で示されたオブジェクト (初期化されているが、まだ削除されていない mutex) の再初期化の試行が検出されました。
EINVAL
説明:mattr 属性値が無効です。その mutex は変更されていません。
EFAULT
説明:mp が指す mutex のアドレスが無効です。
堅牢な mutex の所有者がその mutex をロック解除しないで終了すると、その mutex はロック解除され、不整合としてマークされます。次の所有者が EOWNERDEAD のリターンコードを持つロックを獲得します。
pthread_mutex_consistent_np() は、その所有者が処理を終了したあとも mutex オブジェクト mutex の整合性を保持します。
#include <pthread.h> int pthread_mutex_consistent_np(pthread_mutex_t *mutex);
不整合の mutex を獲得するには、pthread_mutex_lock() を呼び出します。戻り値 EOWNERDEAD は不整合な mutex であることを示します。
pthread_mutex_consistent_np() は、pthread_mutex_lock() への前の呼び出しによって獲得された 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() は失敗します。
EINVAL
説明:現在のスレッドが mutex を所有していないか、または mutex が不整合の状態を持つ PTHREAD_MUTEX_ROBUST_NP mutex ではありません。
mutex が指す mutex をロックするには、pthread_mutex_lock(3C) を使用します。
int pthread_mutex_lock(pthread_mutex_t *mutex);
#include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_ mutex_lock(&mp); /* acquire the mutex */
mutex は、pthread_mutex_lock() から制御が戻るとロックされます。呼び出しスレッドが所有者になります。mutex が別のスレッドによってすでにロックされている (所有されている) 場合は、呼び出しスレッドは mutex が使用可能になるまでブロックされます。
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 は PTHREAD_MUTEX_NORMAL と同じです。
pthread_mutex_lock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EAGAIN
説明:mutex の再帰的なロックが最大数を超えるため、mutex を獲得できません。
EDEADLK
説明:現在のスレッドがすでにその mutex を獲得しています。
mutex が PTHREAD_MUTEX_ROBUST_NP の堅牢度属性で初期化された場合、pthread_mutex_lock() は次のいずれかの値を返す可能性があります。
EOWNERDEAD
説明:この mutex の前の所有者が mutex を保持している間に終了しました。現在この mutex は、呼び出し元によって所有されています。呼び出し元は、mutex によって保護された状態を整合させるよう試行する必要があります。
呼び出し元が状態を整合させることができた場合、その mutex に対して pthread_mutex_consistent_np() を呼び出して、mutex をロック解除します。これ以降の pthread_mutex_lock() の呼び出しは正常に動作します。
呼び出し元が状態を整合させることができない場合は、その mutex に対して pthread_mutex_init() を呼び出さないでください。代わりに mutex をロック解除します。これ以降の pthread_mutex_lock() のすべての呼び出しは mutex の獲得に失敗し、エラーコード ENOTRECOVERABLE を返します。
EOWNERDEAD を持つロックを獲得した所有者が mutex を保持している間に終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。
ENOTRECOVERABLE
説明:獲得しようとしている mutex は、この mutex の前の所有者によって回復不能のままにされた状態を保護していました。mutex は獲得されませんでした。この回復可能な状態は、次のような場合に発生します。
以前、このロックが EOWNERDEAD によって取得された
所有者が状態をクリーンアップできなかった
所有者が mutex の状態を整合させないで mutex をロック解除した
ENOMEM
説明:同時に保持される mutex の上限数を超えています。
mutex が指す mutex をロック解除するには、pthread_mutex_unlock(3C) を使用します。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_unlock(&mutex); /* release the mutex */
pthread_mutex_unlock() は、mutex が指す mutex オブジェクトを解放します。mutex を解放する方法は、mutex の 型属性に依存します。pthread_mutex_unlock() が呼び出されたときに、mutex オブジェクトでブロックされているスレッドがある場合、mutex が使用可能な状態になると、スケジューリングポリシーに基づいて mutex を獲得するスレッドが決定されます。PTHREAD_MUTEX_RECURSIVE のタイプの mutex の場合、mutex が使用可能になるのは、カウントが 0 になり、pthread_mutex_unlock を呼び出したスレッドがこの mutex のロックを解除したときです。
pthread_mutex_unlock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EPERM
説明:現在のスレッドは mutex を所有していません。
mutex が指す mutex のロックを試行し、その mutex がすでにロックされていればただちに戻るには、pthread_mutex_trylock(3C) を使用します。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#include <pthread.h> pthread_mutex_t mutex; int ret; ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */
この関数はブロックしない点を除いて、pthread_mutex_lock() と同じ働きをします。()mutex が参照している mutex オブジェクトが、現在のスレッドを含むいずれかのスレッドによってロックされている場合は、呼び出しはただちに返されます。mutex オブジェクトがロックされていなければ、呼び出しスレッドがロックを獲得します。
pthread_mutex_trylock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EBUSY
説明:mutex が指している mutex はすでにロックされているため、獲得できません。
EAGAIN
説明:mutex に繰り返し行われたロック回数が最大数を超えるため、mutex を所有できません。
シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されています。さらに、pthread_mutexattr_setrobust_np() の引数 robustness が PTHREAD_MUTEX_ROBUST_NP の場合、この関数は失敗し、次のいずれかの値を返します。
EOWNERDEAD
説明:「pthread_mutex_lock の戻り値」の説明を参照してください。
ENOTRECOVERABLE
説明:「pthread_mutex_lock の戻り値」の説明を参照してください。
ENOMEM
説明:同時に保持される mutex の上限数を超えています。
指定した時間まで mutex オブジェクトのロックを試行するには、pthread_mutex_timedlock(3C) 関数を使用します。
この関数は無期限にブロックされることがない点を除いて、pthread_mutex_lock() 関数と同じ動作をします。その mutex がすでにロックされている場合は、mutex が使用可能になるまで呼び出しスレッドはブロックされますが、それもタイムアウトに達するまでの間だけです。mutex が使用可能になる前にタイムアウトが発生すると、関数は終了します。
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
#include <pthread.h> #include <time.h> pthread_mutex_t mutex; timestruct_t abs_timeout; int ret; ret = pthread_mutex_timedlock(&mutex, &abs_timeout);
pthread_mutex_timedlock() 関数は、mutex を正常にロックすると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:mutex が PTHREAD_PRIO_PROTECT の値を持つプロトコル属性で作成されており、呼び出しスレッドの優先順位がこの mutex の現在の優先順位上限を超えています。
説明:mutex で指定された値が、初期化された mutex オブジェクトを表していません。
説明:プロセスまたはスレッドがブロックされており、abs_timeout パラメータで指定されたナノ秒のフィールド値が 0 未満または 10 億以上です。
ETIMEDOUT
説明:指定されたタイムアウトの期限が切れる前に mutex をロックできませんでした。
「pthread_mutex_lock の戻り値」の説明を参照してください。
指定した時間が経過するまで mutex オブジェクトのロックを試行するには、pthread_mutex_reltimedlock_np(3C) 関数を使用します。
タイムアウトの期限が切れるのは、rel_timeout で指定された時間間隔 (CLOCK_REALTIME クロックで測定される) が経過した場合、または呼び出し時に rel_timeout で指定された時間間隔が負である場合です。
int pthread_mutex_reltimedlock_np(pthread_mutex_t *restrict mutex, const struct timespec *restrict rel_timeout);
#include <pthread.h> #include <time.h> pthread_mutex_t mutex; timestruct_t rel_timeout; int ret; ret = pthread_mutex_reltimedlock_np(&mutex, &rel_timeout);
pthread_mutex_reltimedlock_np() 関数は、mutex を正常にロックすると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:mutex が PTHREAD_PRIO_PROTECT の値を持つプロトコル属性で作成されており、呼び出しスレッドの優先順位がこの mutex の現在の優先順位上限を超えています。
説明:mutex で指定された値が、初期化された mutex オブジェクトを表していません。
説明:プロセスまたはスレッドがブロックされており、abs_timeout パラメータで指定されたナノ秒のフィールド値が 0 未満または 10 億以上です。
ETIMEDOUT
説明:指定されたタイムアウトの期限が切れる前に mutex をロックできませんでした。
「pthread_mutex_lock の戻り値」の説明を参照してください。
mp が指す mutex に関連付けられた状態を削除するには、pthread_mutex_destroy(3C) を使用します。
int pthread_mutex_destroy(pthread_mutex_t *mp);
#include <pthread.h> pthread_mutex_t mp; int ret; ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */
mutex の記憶領域は解放されません。
pthread_mutex_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。
EINVAL
説明:mp で指定された値が、初期化された mutex オブジェクトを表していません。
例 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 つの関数がそれぞれ別の目的で相互排他ロックを使用しています。increment_count() 関数は、相互排他ロックによって共有変数の不可分操作による更新を保証しています。get_count() 関数は、相互排他ロックによって 64 ビット 値の count が不可分に読み取られるようにしています。32 ビットアーキテクチャーでは、long
long
は実際には 2 つの 32 ビット値として処理されます。
整数はほとんどのマシンで共通のワードサイズであるため、整数値の読み取りは不可分操作です。
同時に 2 つのリソースをアクセスすることがあります。一方のリソースを使用しているとき、もう一方のリソースも必要となる場合があります。2 つのスレッドが同じ 2 つのリソースを要求しようとして、対応する相互排他ロックを異なる順序で獲得しようとすると、問題が発生します。たとえば、2 つのスレッドがそれぞれ mutex の 1 と 2 をロックし、次に各スレッドが互いにもう一方の mutex をロックしようとすると、デッドロックが発生します。例 4–2 に、デッドロックが発生する場合のシナリオを示します。
この問題を回避する最善の方法は、スレッドで複数の mutex をロックする場合、常に同じ順序でロックすることです。ロックが常に規定された順序で実行されれば、デッドロックは起こらないはずです。この方法を「ロック階層」と呼びます。この方法では、mutex に論理的な番号を割り振って、mutex に順序を付けます。
自分が持つ mutex の番号より小さい番号が割り振られている mutex はロックできないという規定を守るようにします。
ただし、この方法が使用できない場合もあります。規定と違う順序で相互排他ロックを獲得しなければならないこともあるからです。そのような状況でデッドロックを防ぐには、pthread_mutex_trylock() を使用します。デッドロックが避けられないような事態が生じた場合は、あるスレッドが現在保持している 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 を慎重にロックしなければなりません。これは、スレッド 2 が mutex 1 が解放されるまで待つとすると、スレッド 1 との間にデッドロックの関係が生じる恐れがあるからです。
デッドロックの発生を防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex が使用可能な状態であれば獲得します。mutex が使用可能な状態でない場合、スレッド 2 はただちに終了し、エラーを返します。その時点で、スレッド 2 は mutex 2 を解放しなければなりません。その結果、スレッド 1 は mutex 2 をロックでき、その後 mutex 1 と mutex 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 が削除されることはありません。
この検索を同時並行的に行われる削除から保護するために、各ノードをロックしてからノードの内容にアクセスしなければなりません。すべての検索が ListHead の位置から開始されるので、常にリストの順序でロックされます。このため、デッドロックが発生することはありません。
目的のノードが見つかったら、この変更がそのノードと直前のノードの両方に影響を与えるため、両方をロックします。直前のノードのロックが常に最初に獲得されるので、ここでもデッドロックの心配はありません。例 4–5 は、片方向リンクリストから特定のノードを削除する C コードです。
node1_t *delete(int value) { node1_t *prev, *current; prev = &ListHead; pthread_mutex_lock(&prev->lock); while ((current = prev->link) != NULL) { pthread_mutex_lock(¤t->lock); if (current->value == value) { prev->link = current->link; pthread_mutex_unlock(¤t->lock); pthread_mutex_unlock(&prev->lock); current->link = NULL; return(current); } pthread_mutex_unlock(&prev->lock); prev = current; } pthread_mutex_unlock(&prev->lock); return(NULL); }
例 4–6 は、前述のリスト構造を修正して循環リストにしたものです。先頭のノードとして識別されるノードはありません。スレッドは適当な 1 つのノードに関連付けられ、そのノードと近傍ノードに対して操作を行います。この状況ではロック序列は適用できません。明らかに階層 (つまり、リンクをたどる順番) が循環的だからです。
typedef struct node2 { int value; struct node2 *link; pthread_mutex_t lock; } node2_t;
以下は、2 つのノードをロックし、両方のノードに対してある操作を行う C コードです。
void Hit Neighbor(node2_t *me) { while (1) { pthread_mutex_lock(&me->lock); if (pthread_mutex_trylock(&me->link->lock)!= 0) { /* failed to get lock */ pthread_mutex_unlock(&me->lock); continue; } break; } me->link->value += me->value; me->value /=2; pthread_mutex_unlock(&me->link->lock); pthread_mutex_unlock(&me->lock); }