この章では、スレッドで使用できる同期のタイプについて説明します。また、同期を使用する場合と方法についても説明します。
同期オブジェクトは、データと同じようにしてアクセスされるメモリー内の変数です。異なるプロセスのスレッドは、スレッドによって制御されている共有メモリー内の同期オブジェクトによって情報をやり取りできます。通常、異なるプロセスのスレッドは互いに見えませんが、これらのスレッドは情報をやり取りできます。
同期オブジェクトをファイル内に配置することもできます。そうすれば、作成元のプロセスの消滅後も同期オブジェクトは存続します。
次の同期オブジェクトがあります。
相互排他ロック
条件変数
読み取り/書き込みロック
セマフォー
同期は、次のような状況で効果を発揮します。
同期が、共有データの整合性を保証する唯一の手段である場合。
異なるプロセス内のスレッド間で同じ同期オブジェクトを共同で使用する場合。同期オブジェクトを初期化するのは、連携するそれらのプロセスの中の 1 つのプロセスに限るべきです。同期オブジェクトを初期化し直すと、そのロック状態が解除されることになるからです。
同期によって可変データの安全性を保証できる場合。
プロセスがファイルをマッピングし、自分のスレッドにレコード形式のロックを獲得させることができる場合。ロックがいったん獲得されると、そのファイルをマッピングしているプロセス内のスレッドのうち、ロックを保持しているスレッド以外がそのロックを獲得しようとすると、そのロックが解放されるまでブロックされます。
整数のような単一の基本的な変数にアクセスするときには、1 回のメモリーロードで複数のメモリーサイクルを使用することがあります。整数がバスのデータ幅にそろっていない場合や、バスのデータ幅より大きい場合、複数のメモリーサイクルが必要になります。こうした状況は SPARC 版アーキテクチャーのマシンでは発生しませんが、プログラムの移植性を考慮すると、この問題は無視できません。
32 ビットアーキテクチャーでは、long
long
は不可分ではありません。不可分操作は、それ以上小さい操作に分割できません。long
long
は、2 つの 32 ビット値として読み書きされます。int
型、char
型、float
型、およびポインタは、SPARC アーキテクチャーマシンと x86 マシンでは不可分です。
相互排他ロック (mutex) は、スレッドの実行を直列化したいときに使用します。相互排他ロックでスレッド間の同期をとるときは、通常、コードの危険領域が複数のスレッドによって同時に実行されないようにするという方法を使用します。単一のスレッドのコードを保護する目的で相互排他ロックを使用することもできます。
デフォルトの mutex 属性を変更するには、属性オブジェクトを宣言して初期化します。多くの場合、アプリケーションの先頭部分の一箇所で設定しますので、mutex 属性は、すばやく見つけて簡単に変更できます。表 4–1 に、mutex 属性を操作する関数を示します。
表 4–1 mutex 属性ルーチン
操作 |
参照先 |
---|---|
mutex 属性オブジェクトの初期化 | |
mutex 属性オブジェクトの削除 | |
mutex のスコープ設定 | |
mutex のスコープの値の取得 | |
mutex の型属性の設定 | |
mutex の型属性の取得 | |
mutex 属性のプロトコルの設定 | |
mutex 属性のプロトコルの取得 | |
mutex 属性の優先順位上限の設定 | |
mutex 属性の優先順位上限の取得 | |
mutex の優先順位上限の設定 | |
mutex の優先順位上限の取得 | |
mutex の堅牢度属性の設定 | |
mutex の堅牢度属性の取得 |
mutex オブジェクトに関連付けられた属性をデフォルト値に初期化するには、pthread_mutexattr_init(3C) を使用します。各属性オブジェクトのための記憶領域は、実行時にスレッドシステムによって割り当てられます。
int pthread_mutexattr_init(pthread_mutexattr_t *mattr);
#include <pthread.h> pthread_mutexattr_t mattr; int ret; /* initialize an attribute to default value */ ret = pthread_mutexattr_init(&mattr);
mattr は不透明な
型で、システムによって割り当てられた属性オブジェクトを格納します。mattr オブジェクト内の属性については、表 4–2 を参照してください。
mutex 属性オブジェクトを再度初期化する場合は、あらかじめ pthread_mutexattr_destroy(3C) を呼び出して、オブジェクトを削除しておく必要があります。pthread_mutexattr_init() を呼び出すと、不透明なオブジェクトが割り当てられます。そのオブジェクトが削除されないと、結果的にメモリーリークを引き起こします。
表 4–2 mattr のデフォルトの属性値
属性 |
値 |
結果 |
---|---|---|
pshared |
PTHREAD_PROCESS_PRIVATE |
初期化された mutex を 1 つのプロセス内で使用できます。その mutex を操作できるのは同じプロセスで生成されたスレッドだけです。 |
type |
PTHREAD_MUTEX_DEFAULT |
Solaris Pthreads 実装では、PTHREAD_MUTEX_DEFAULT は、デッドロックを検出しない PTHREAD_MUTEX_NORMAL に割り当てられます。 |
プロトコル |
PTHREAD_PRIO_NONE |
スレッドの優先順位とスケジューリングは、そのスレッドが所有する mutex の優先順位の影響を受けません。 |
prioceiling |
– |
prioceiling の値は、sched_get_priority_min() および sched_get_priority_max() 関数によって返される、SCHED_FIFO 方針に関する既存の優先順位の範囲から取得されます。この優先順位の範囲は、mutex が作成された Solaris バージョンによって決定されます。 |
robustness |
PTHREAD_MUTEX_STALLED_NP |
mutex の所有者が終了すると、この mutex に関する pthread_mutex_lock() への将来のすべての呼び出しは進行過程からブロックされます。 |
pthread_mutexattr_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、次の値を返します。
ENOMEM
説明:メモリー不足のため、mutex 属性オブジェクトを初期化できません。
pthread_mutexattr_destroy(3C) は、pthread_mutexattr_init() によって生成された属性オブジェクトの管理に使用されていた記憶領域の割り当てを解除します。
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
#include <pthread.h> pthread_mutexattr_t mattr; int ret; /* destroy an attribute */ ret = pthread_mutexattr_destroy(&mattr);
pthread_mutexattr_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:mattr で指定された値が無効です。
pthread_mutexattr_setpshared(3C) は、mutex 変数のスコープを設定します。
int pthread_mutexattr_setpshared(pthread_mutexattr_t *restrict mattr, int *restrict pshared);
#include <pthread.h> pthread_mutexattr_t mattr; int ret; ret = pthread_mutexattr_init(&mattr); /* * resetting to its default value: private */ ret = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_PRIVATE);
mutex 変数のスコープは、プロセス専用 (プロセス内) またはシステム共通 (プロセス間) です。複数のプロセスのスレッド間で mutex を共有させるには、pshared 属性を PTHREAD_PROCESS_SHARED に設定して、共有メモリー内に mutex を生成します。
mutex の pshared 属性を PTHREAD_PROCESS_PRIVATE に設定した場合、その mutex を操作できるのは同じプロセスで生成されたスレッドだけです。
pthread_mutexattr_setpshared () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:mattr で指定された値が無効です。
pthread_mutexattr_getpshared(3C) は、pthread_mutexattr_setpshared() によって定義された mutex 変数のスコープを返します。
int pthread_mutexattr_getpshared(pthread_mutexattr_t *restrict mattr, int *restrict pshared);
#include <pthread.h> pthread_mutexattr_t mattr; int pshared, ret; /* get pshared of mutex */ ret = pthread_mutexattr_getpshared(&mattr, &pshared);
属性オブジェクト mattr の pshared の現在値を取得します。これは PTHREAD_PROCESS_SHARED と PTHREAD_PROCESS_PRIVATE のどちらかです。
pthread_mutexattr_getpshared () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:mattr で指定された値が無効です。
pthread_mutexattr_settype(3C) は、mutex の型 (type) 属性を設定します。
#include <pthread.h> int pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);
型 (type) 属性のデフォルト値は PTHREAD_MUTEX_DEFAULT です。
型 (type) 引数は mutex の型を指定します。以下に、有効な mutex 型を示します。
PTHREAD_MUTEX_NORMAL
説明:この型の mutex はデッドロックを検出しません。スレッドが、この mutex をロック解除しないでもう一度ロックしようとすると、mutex はデッドロックします。別のスレッドによってロックされた mutex をロック解除しようとした場合、引き起こされる動作は未定義です。また、ロック解除された mutex をロック解除しようとした場合、引き起こされる動作は不定です。
PTHREAD_MUTEX_ERRORCHECK
説明:この型の mutex はエラーチェックを行います。スレッドがこの mutex をロック解除しないでもう一度ロックしようとすると、エラーを返します。あるスレッドがロックした mutex を別のスレッドがロック解除しようとすると、エラーが返されます。また、ロック解除された mutex をロック解除しようとするとエラーを返します。
PTHREAD_MUTEX_RECURSIVE
説明:スレッドがこの mutex をロック解除しないでもう一度ロックしようとすると、正常にロックできます。PTHREAD_MUTEX_NORMAL 型の mutex ではロックを繰り返すとデッドロックが発生しますが、この型の mutex では発生しません。複数回ロックされた mutex を別のスレッドが獲得するときには、その前に同じ回数ロック解除する必要があります。あるスレッドがロックした mutex を別のスレッドがロック解除しようとすると、エラーが返されます。また、ロック解除された mutex をロック解除しようとするとエラーを返します。
PTHREAD_MUTEX_DEFAULT
説明:この属性は、ほかの mutex 型に割り当てることができます。Solaris の実装では、この属性は PTHREAD_PROCESS_NORMAL に割り当てられます。
pthread_mutexattr_settype 関数は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:type または attr の値が無効です。
pthread_mutexattr_gettype(3C) は、pthread_mutexattr_settype() によって設定された mutex の型 (type) 属性を取得します。
#include <pthread.h> int pthread_mutexattr_gettype(pthread_mutexattr_t *restrict attr , int *restrict type);
型 (type) 属性のデフォルト値は PTHREAD_MUTEX_DEFAULT です。
型 (type) 引数は mutex の型を指定します。有効な mutex 型を以下に示します。
PTHREAD_MUTEX_NORMAL
PTHREAD_MUTEX_ERRORCHECK
PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_DEFAULT
それぞれの型については、「pthread_mutexattr_settype の構文」を参照してください。
pthread_mutexattr_gettype() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:type で指定された値が無効です。
pthread_mutexattr_setprotocol(3C) は、mutex 属性オブジェクトのプロトコル属性を設定します。
#include <pthread.h> int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。
protocol には、mutex 属性オブジェクトに適用されるプロトコルを指定します。
pthread.h に定義されている protocol の値は、次の値のうちのいずれか 1 つです。 PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT、または PTHREAD_PRIO_PROTECT です。
PTHREAD_PRIO_NONE
PTHREAD_PRIO_INHERIT
このプロトコル値は、所有しているスレッドの優先順位とスケジューリングに影響します。より優先順位の高いスレッドが thrd1 の所有する 1 つまたは複数の mutex 上でブロックされている場合、これらの mutex が PTHREAD_PRIO_INHERIT を使用して初期化されていると、thrd1 はより高い優先順位、すなわち thrd1 が所有する mutex を待っているスレッドの最高優先順位で実行されます。
thrd1 が別のスレッド thrd3 が所有する mutex をブロックしている場合、同様の優先順位継承効果が thrd3 に対して再帰的に伝播されます。
優先順位の逆転を避けるには、 PTHREAD_PRIO_INHERIT を使用します。優先順位の低いスレッドが、そのスレッドより優先順位の高いスレッドが必要としているロックを保持していると、優先順位が逆転します。優先順位の高いスレッドは、優先順位の低いスレッドがロックを解除するまで実行を続行できません。
優先順位の継承を使用しない場合は、優先順位の低いスレッドが長期間実行をスケジュールされないために、優先順位の高いスレッドが同じ期間ブロックされる可能性があります。優先順位の継承では、優先順位の低いスレッドの優先順位を一時的に上げることによって、そのスレッドがすばやく実行をスケジュールされてロックを解放し、それによって優先順位の高いスレッドがそのロックを獲得できるようにします。優先順位の低いスレッドは、ロックを解放すると、元の低い優先順位に戻ります。
PTHREAD_PRIO_PROTECT
スレッドが PTHREAD_PRIO_PROTECT で初期化された mutex を 1 つ以上所有している場合、このプロトコル値は、スレッド (thrd2 など) の優先順位とスケジューリングに影響します。thrd2 は、より高い優先順位または自分が所有しているすべての mutex の中でもっとも高い優先順位で実行します。thrd2 が所有するいずれかの mutex でブロックされているより優先度の高いスレッドは、thrd2 のスケジューリングには影響を与えません。
PTHREAD_PRIO_INHERIT および PTHREAD_PRIO_PROTECT mutex 属性を使用できるのは、リアルタイム (RT) スケジューリングクラス SCHED_FIFO または SCHED_RR で実行されている特権付きプロセスだけです。
スレッドは、 PTHREAD_PRIO_INHERIT で初期化された mutex と PTHREAD_PRIO_PROTECT で初期化された mutex を複数同時に所有できます。この場合、スレッドは、これらのプロトコルのいずれかで獲得された最高の優先順位で実行します。
pthread_mutexattr_setprotocol() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次のどちらかの条件が検出されると、pthread_mutexattr_setprotocol() は失敗し、対応する値を返します。
EINVAL
説明:attr または protocol に指定した値は無効です。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_getprotocol(3C) は、mutex 属性オブジェクトのプロトコル属性を取得します。
#include <pthread.h> int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol);
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。
protocol には、次のプロトコル属性のうちいずれか 1 つが含まれています。 PTHREAD_PRIO_NONE、PTHREAD_PRIO_INHERIT、または PTHREAD_PRIO_PROTECT です。これらはヘッダー <pthread.h> で定義されています。
pthread_mutexattr_getprotocol() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次のどちらかの条件が検出されると、pthread_mutexattr_getprotocol() は失敗し、条件に対応する値を返します。
EINVAL
説明:attr で指定された値が NULL か、attr または protocol で指定された値が無効です。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_setprioceiling(3C) は、mutex 属性オブジェクトの優先順位上限属性を設定します。
#include <pthread.h> int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, int prioceiling);
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。
prioceiling には、初期化された mutex の優先順位上限を指定します。この上限は、mutex によって保護されている重要領域が実行される最小の優先レベルを定義します。prioceiling は、SCHED_FIFO によって定義される優先順位の最大範囲内にあります。優先順位が逆転しないように、特定の mutex をロックするすべてのスレッドの中でもっとも高い優先順位と同じかまたはそれを上回る優先順位を prioceiling として設定します。
pthread_mutexattr_setprioceiling() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次のどちらかの条件が検出されると、pthread_mutexattr_setprioceiling() は失敗し、対応する値を返します。
EINVAL
説明:attr で指定された値が NULL または無効であるか、prioceiling が無効です。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_getprioceiling(3C) は、mutex 属性オブジェクトの優先順位上限属性を取得します。
#include <pthread.h> int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *restrict attr, int *restrict prioceiling);
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された属性オブジェクトを指します。
pthread_mutexattr_getprioceiling() は、初期化された mutex の優先順位上限を prioceiling で返します。この上限は、mutex によって保護されている重要領域が実行される最小の優先レベルを定義します。prioceiling は、SCHED_FIFO によって定義される優先順位の最大範囲内にあります。優先順位が逆転しないように、特定の mutex をロックするすべてのスレッドの中でもっとも高い優先順位と同じかまたはそれを上回る優先順位を prioceiling として設定します。
pthread_mutexattr_getprioceiling() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次のどちらかの条件が検出されると、pthread_mutexattr_getprioceiling() は失敗し、対応する値を返します。
EINVAL
説明:attr で指定された値が NULL です。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_setprioceiling(3C) は、mutex の優先順位上限を設定します。
#include <pthread.h> int pthread_mutex_setprioceiling(pthread_mutex_t *restrict mutex, int prioceiling, int *restrict old_ceiling);
pthread_mutex_setprioceiling() は mutex の優先順位上限、つまり prioceiling を変更します。pthread_mutex_setprioceiling() は、mutex のロックが解除されている場合 mutex をロックするか、mutex を正常にロックできるようになるまでブロックして、mutex の優先順位上限を変更し、mutex を開放します。()mutex をロックするプロセスでは、優先順位保護プロトコルを守る必要はありません。
pthread_mutex_setprioceiling() が正常に終了すると、優先順位上限の以前の値が old_ceiling で返されます。pthread_mutex_setprioceiling() が失敗すると、mutex の優先順位上限は元のままになります。
pthread_mutex_setprioceiling() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次のいずれかの条件が検出されると、pthread_mutex_setprioceiling() は失敗し、対応する値を返します。
EINVAL
説明:prioceiling で要求された優先順位が範囲外です。
EINVAL
説明:mutex が、THREAD_PRIO_PROTECT の値を持つ protocol 属性で初期化されていませんでした。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_getprioceiling(3C) は、mutex の優先順位上限を取得します。
#include <pthread.h> int pthread_mutex_getprioceiling(const pthread_mutex_t *restrict mutex, int *restrict prioceiling);
pthread_mutex_getprioceiling() は、mutex の優先順位上限、つまり prioceiling を返します。
pthread_mutex_getprioceiling() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次の条件が検出されると、pthread_mutexatt_getprioceiling() は失敗し、対応する値を返します。
次のいずれかの条件が検出されると、pthread_mutex_getprioceiling() は失敗し、対応する値を返します。
EINVAL
説明:mutex で指定された値は現在の既存の mutex を参照していません。
EPERM
説明:呼び出し元はこの操作を行うための権限を持っていません。
pthread_mutexattr_setrobust_np(3C) は、mutex 属性オブジェクトの堅牢度属性を設定します。
#include <pthread.h> int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, int *robustness);
pthread_mutexattr_setrobust_np() が適用されるのは、シンボル _POSIX_THREAD_PRIO_INHERIT が定義されている場合だけです。
Solaris 10 および以前のリリースでは、PTHREAD_MUTEX_ROBUST_NP 属性は、自身も PTHREAD_PRIO_INHERIT プロトコル属性でマークされている mutex にのみ適用できます。この制限は、それ以降の Solaris リリースでは解消されています。
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。
robustness は、mutex の所有者が (通常はプロセスが異常終了したために) mutex をロック解除しないで終了した場合の動作を定義します。pthread.h に定義可能な robustness の値は、PTHREAD_MUTEX_ROBUST_NP または PTHREAD_MUTEX_STALLED_NP です。デフォルト値は、PTHREAD_MUTEX_STALLED_NP です。
PTHREAD_MUTEX_STALLED_NP
mutex の所有者が mutex をロック解除しないで終了すると、それ以降の pthread_mutex_lock() へのすべての呼び出しは、指定しない方法で進行過程からブロックされます。
PTHREAD_MUTEX_ROBUST_NP
mutex の所有者が mutex をロック解除しないで終了すると、mutex はロック解除されます。この mutex の次の所有者がその mutex を獲得し、エラーコード EOWNERDEAD が返されます。
アプリケーションは pthread_mutex_lock() からのリターンコードを常にチェックして、PTHREAD_MUTEX_ROBUST_NP 属性で初期化された mutex がないかどうかを確認する必要があります。
この mutex の新しい所有者は、mutex によって保護されている状態を整合させる必要があります。これは、前の所有者が終了したときに状態が不整合のままになっている可能性があるためです。
新しい所有者が状態を整合できる場合は、その mutex に対して pthread_mutex_consistent_np() を呼び出してから mutex をロック解除します。これによって mutex は整合性があるとしてマークされるため、以降の pthread_mutex_lock() および pthread_mutex_unlock() の呼び出しが通常の方法で動作します。
新しい所有者が状態を整合できない場合は、その mutex に対して pthread_mutex_consistent_np() を呼び出さずに、mutex をロック解除してください。
すべての待機者が呼び起こされ、それ以降の pthread_mutex_lock() へのすべての呼び出しは mutex の獲得に失敗し、エラーコード ENOTRECOVERABLE を返します。この時点で、pthread_mutex_destroy() を呼び出して mutex を削除し、pthread_mutex_int() を呼び出して初期化し直すことによって、mutex の状態を整合させることができます。ただし、この mutex によって保護されていた状態は不整合のままになるため、何らかの形式のアプリケーション復旧が必要になります。
EOWNERDEAD を持つロックを獲得したスレッドが mutex をロック解除しないで終了すると、次の所有者が EOWNERDEAD のリターンコードを持つロックを獲得します。
pthread_mutexattr_setrobust_np() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次の条件が検出されると、pthread_mutexattr_setrobust_np() は失敗する可能性があります。
EINVAL
説明:attr または robustness で指定された値は無効です。
pthread_mutexattr_getrobust_np(3C) は、mutex 属性オブジェクトの堅牢度属性を取得します。
#include <pthread.h> int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, int *robustness);
attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。
robustness は、mutex 属性オブジェクトの堅牢度属性の値です。
pthread_mutexattr_getrobust_np() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
次の条件が検出されると、pthread_mutexattr_getrobust_np() は失敗する可能性があります。
EINVAL
説明:attr または robustness で指定された値は無効です。
表 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); }
スピンロックは、主に共有メモリー型のマルチプロセッサ上での使用に適した低レベルの同期機構です。呼び出しスレッドが、すでに別のスレッドによって保持されているスピンロックを要求する場合、2 番目のスレッドは、そのロックが使用可能になったかどうかをテストするためのループに入ります。スピンはプロセッササイクルを浪費するため、ロックを獲得したら、短時間だけ保持するようにすべきです。呼び出し元は、ほかのスレッドがロックを獲得できるようにするためのスリープ操作を呼び出す前に、スピンロックを解除するようにしてください。
スピンロックは mutex と条件変数を使用して実装することもできますが、スピンロックを実行するための標準化された方法は pthread_spin_* 関数です。短期間のロックであれば、pthread_spin_* 関数に必要なオーバーヘッドははるかに少なくなります。
どういうロックを実行する場合も、スレッドのブロックを設定している間に消費されるプロセッサリソースと、ブロックされている間にスレッドによって消費されるプロセッサリソースとの間にトレードオフが発生します。スピンロックでは、スレッドのブロックを設定したあと、単純なループを実行して、ロックが使用可能になるまで不可分なロック処理を繰り返すためのリソースがほとんど必要ありません。スレッドは、待機している間もプロセッサリソースを消費し続けます。
スピンロックに比べると、mutex はスレッドのブロックにより大量のプロセッサリソースを消費します。相互排他ロックが使用できない場合、スレッドはスケジューリングの状態を変更して、自身を待機スレッドの待ち行列に追加します。ロックが使用可能になると、スレッドがロックを獲得する前に、これらの手順を逆にたどる必要があります。スレッドは、ブロックされている間、プロセッサリソースを消費しません。
したがって、スピンロックと mutex は別の目的に使用すると有効な場合があります。非常に短期間のブロックでは、スピンロックの方が全体的なオーバーヘッドは少なくなることがあります。スレッドがより長期間ブロックされる場合は、mutex の方が全体的なオーバーヘッドは少なくなることがあります。
スピンロックを使用するために必要なリソースを割り当て、ロックをロック解除状態に初期化するには、pthread_spin_init(3C) 関数を使用します。
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
#include <pthread.h> pthread_spinlock_t lock; int pshared; int ret; /* initialize a spin lock */ ret = pthread_spin_init(&lock, pshared);
pshared 属性は、次のいずれかの値を持ちます。
PTHREAD_PROCESS_SHARED
説明:スピンロックが割り当てられているメモリーにアクセスできるすべてのスレッドに、スピンロックの操作を許可します。このロックが複数のプロセスによって共有されているメモリーに割り当てられている場合にも、ロックの操作を許可します。
PTHREAD_PROCESS_PRIVATE
説明:スピンロックを、そのスピンロックを初期化したスレッドと同じプロセス内で作成されたスレッドだけが操作できるようにします。異なるプロセスのスレッドからスピンロックを操作しようとした場合、その結果は未定義です。プロセス共有の属性のデフォルト値は、PTHREAD_PROCESS_PRIVATE です。
pthread_spin_init() 関数は、正常終了時に 0 を返します。それ以外の場合は、次のいずれかのエラーコードが返されます。
EAGAIN
説明:別のスピンロックを初期化するために必要なシステムリソースが不足しています。
EBUSY
説明:スピンロックが別のスレッドで使用されている間 (たとえば、pthread_spin_lock() の呼び出しで使用されている間) にそのロックの初期化または削除の試行が検出されました。
EINVAL
説明:lock で指定された値が無効です。
スピンロックを獲得するには、pthread_spin_lock(3C) を使用します。呼び出しスレッドは、ロックが別のスレッドによって保持されていなければ、そのロックを獲得します。それ以外の場合、スレッドは、そのロックが使用可能になるまで pthread_spin_lock() 呼び出しから復帰しません。呼び出し時に、呼び出しスレッドがロックを保持している場合の結果は不定です。
int pthread_spin_lock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_ spin_lock(&lock); /* lock the spinlock */
pthread_spin_lock() 関数は、正常終了時に 0 を返します。それ以外の場合は、次のいずれかのエラーコードが返されます。
EDEADLK
説明:現在のスレッドがすでにそのスピンロックを獲得しています。
EINVAL
説明:lock で指定された値が、初期化されたスピンロックオブジェクトを表していません。
スピンロックを獲得し、ロックが別のスレッドによって保持されている場合はただちに失敗するには、pthread_spin_trylock(3C) 関数を使用します。
int pthread_spin_trylock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_trylock(&lock); /* try to lock the spin lock */
pthread_spin_trylock() 関数は、正常終了時に 0 を返します。それ以外の場合は、次のいずれかのエラーコードが返されます。
EBUSY
説明:スレッドが現在スピンロックを所有しています。
EINVAL
説明:lock で指定された値が、初期化されたスピンロックオブジェクトを表していません。
獲得されたスピンロックを解放するには、pthread_spin_unlock(3C) 関数を使用します。
int pthread_spin_unlock(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_unlock(&lock); /* spinlock is unlocked */
pthread_spin_unlock() 関数は、正常終了時に 0 を返します。それ以外の場合は、次のいずれかのエラーコードが返されます。
EPERM
説明:呼び出しスレッドがロックを保持していません。
EINVAL
説明:lock で指定された値が、初期化されたスピンロックオブジェクトを表していません。
スピンロックを削除し、そのロックによって使用されているリソースをすべて解放するには、pthread_spin_destroy(3C) 関数を使用します。
int pthread_spin_destroy(pthread_spinlock_t *lock);
#include <pthread.h> pthread_spinlock_t lock; int ret; ret = pthread_spin_destroy(&lock); /* spinlock is destroyed */
削除したロックを、pthread_spin_init() を呼び出して再び初期化する前に使用した場合、その結果は不定です。スレッドがロックを保持しているときに pthread_spin_destroy() が呼び出された場合、またはスレッドの初期化されていないスピンロックに対してこの関数が呼び出された場合の結果は不定です。
EBUSY
説明:スピンロックが別のスレッドで使用されている間 (たとえば、pthread_spin_lock() の呼び出しで使用されている間) にそのロックの初期化または削除の試行が検出されました。
EINVAL
説明:lock で指定された値が無効です。
条件変数は、ある条件が真になるまでスレッドを不可分にブロックしたいときに使用します。必ず相互排他ロックとともに使用します。
条件変数を使うと、特定の条件が真になるまでスレッドを不可分にブロックできます。この条件判定は、相互排他ロックにより保護された状態で行います。
条件が偽のとき、スレッドは通常は条件変数でブロック状態に入り、相互排他ロックを原子的操作により解除して、条件が変更されるのを待ちます。別のスレッドによって条件が変更されると、そのスレッドは関連した条件変数にシグナルを送り、1 つ以上の待機スレッドに次の処理を実行させることができます。
呼び起こす
相互排他ロックを再度獲得する
条件を再評価する
次の条件のプロセスの間でスレッドの同期を取るには、条件変数を使用します。
スレッドが書き込み可能なメモリーに割り当てられている
連携する複数のプロセスがメモリーを共有している
スケジューリングポリシーは、ブロックされたスレッドがどのように呼び起こされるかを決定します。デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドが呼び起こされる順序を指定していません。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、スレッドは優先順位に従って呼び起こされます。
条件変数の属性は、使用する前に設定して初期化しておかなければなりません。表 4–4 に、条件変数の属性を操作する関数を示します。
表 4–4 条件変数の属性
操作 |
参照先 |
---|---|
条件変数の属性の初期化 | |
条件変数の属性の削除 | |
条件変数のスコープの設定 | |
条件変数のスコープの取得 | |
クロック選択条件変数の属性の取得 | |
クロック選択条件変数の属性の設定 |
このオブジェクトに割り当てられた属性をデフォルト値に初期化するには、pthread_condattr_init(3C) を使用します。各属性オブジェクトのための記憶領域は、実行時にスレッドシステムによって割り当てられます。
int pthread_condattr_init(pthread_condattr_t *cattr);
#include <pthread.h> pthread_condattr_t cattr; int ret; /* initialize an attribute to default value */ ret = pthread_condattr_init(&cattr);
この関数が呼び出されたときの pshared 属性のデフォルト値は PTHREAD_PROCESS_PRIVATE です。 pshared がこの値の場合は、初期化された条件変数がプロセス内で使用可能であることを示します。
cattr は不透明なデータ型で、システムによって割り当てられた属性オブジェクトを格納します。cattr のスコープとして取り得る値は、PTHREAD_PROCESS_PRIVATE と PTHREAD_PROCESS_SHARED です。PTHREAD_PROCESS_PRIVATE はデフォルト値です。
条件変数の属性を再利用するには、まず、この属性を pthread_condattr_destroy(3C) で初期化し直す必要があります。pthread_condattr_init() 呼び出しは、不透明なオブジェクトへのポインタを戻します。そのオブジェクトが削除されないと、結果的にメモリーリークを引き起こします。
pthread_condattr_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。
ENOMEM
説明:メモリーが足りなくて、スレッド属性オブジェクトを初期化できません。
EINVAL
説明:cattr で指定された値が無効です。
記憶領域を削除し、属性オブジェクトを無効にするには、pthread_condattr_destroy(3C) を使用します。
int pthread_condattr_destroy(pthread_condattr_t *cattr);
#include <pthread.h> pthread_condattr_t cattr; int ret; /* destroy an attribute */ ret = pthread_condattr_destroy(&cattr);
pthread_condattr_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:cattr で指定された値が無効です。
pthread_condattr_setpshared(3C) は、条件変数のスコープをプロセス専用 (プロセス内) またはシステム共通 (プロセス間) に設定します。
int pthread_condattr_setpshared(pthread_condattr_t *cattr, int pshared);
#include <pthread.h> pthread_condattr_t cattr; int ret; /* all processes */ ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); /* within a process */ ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);
共有メモリー内に pshared 属性を生成し、その条件変数を PTHREAD_PROCESS_SHARED に設定した場合、この条件変数は複数のプロセスのスレッド間で共有できます。
mutex の pshared 属性を PTHREAD_PROCESS_PRIVATE に設定した場合、その mutex を操作できるのは同じプロセスで生成されたスレッドだけです。PTHREAD_PROCESS_PRIVATE はデフォルト値です。PTHREAD_PROCESS_PRIVATE は、局所条件変数として動作します。PTHREAD_PROCESS_SHARED の動作は、大域条件変数に相当します。
pthread_condattr_setpshared () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:cattr または pshared の値が無効です。
pthread_condattr_getpshared(3C) は、属性オブジェクト cattr の pshared の現在のスコープ値を取得します。
int pthread_condattr_getpshared(const pthread_condattr_t *restrict cattr, int *restrict pshared);
#include <pthread.h> pthread_condattr_t cattr; int pshared; int ret; /* get pshared value of condition variable */ ret = pthread_condattr_getpshared(&cattr, &pshared);
属性オブジェクトの値は、PTHREAD_PROCESS_SHARED か PTHREAD_PROCESS_PRIVATE になります。
pthread_condattr_getpshared () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:cattr の値が無効です。
attr が参照する初期化された属性オブジェクト内のクロック属性を設定するには、pthread_condattr_setclock(3C) 関数を使用します。pthread_condattr_setclock() が、CPU 時間クロックを表す clock_id 引数を指定して呼び出された場合、その呼び出しは失敗します。クロック属性は、pthread_cond_timedwait() のタイムアウトサービスを測定するために使用されるクロックのクロック ID です。クロック属性のデフォルト値は、システムクロック CLOCK_REALTIME を表します。この時点で、クロック属性に対して取り得るほかの値は CLOCK_MONOTONIC だけです。
int pthread_condattr_setclock(pthread_condattr_t attr, clockid_t clock_id);
#include <pthread.h> pthread_condattr_t attr clockid_t clock_id int ret ret = pthread_condattr_setclock(&attr &clock_id
pthread_condattr_setclock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:clock_id で指定された値が既知のクロックを表していないか、または CPU 時間クロックです。
attr が参照する属性オブジェクトからクロック属性の値を取得するには、pthread_condattr_getclock(3C) 関数を使用します。クロック属性は、pthread_cond_timedwait() のタイムアウトサービスを測定するために使用されるクロックのクロック ID です。
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
#include <pthread.h> pthread_condattr_t attr clockid_t clock_id int ret ret = pthread_condattr_getclock(&attr &clock_id
pthread_condattr_getclock() は、正常終了時に 0 を返し、attr の clock 属性の値を clock_id 引数が参照するオブジェクトに格納します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:attr の値が無効です。
この節では条件変数の使用方法を説明します。表 4–5 に、使用可能な関数を示します。
表 4–5 条件変数関数
操作 |
参照先 |
---|---|
条件変数の初期化 | |
条件変数によるブロック | |
特定のスレッドのブロック | |
時刻指定のブロック | |
間隔指定のブロック | |
全スレッドのブロック解除 | |
条件変数の削除 |
cv が指す条件変数をデフォルト値に初期化する場合や、すでに pthread_condattr_init() で設定されている条件変数を指定する場合は、pthread_cond_init(3C) を使用します。
int pthread_cond_init(pthread_cond_t *restrict cv, const pthread_condattr_t *restrict cattr);
#include <pthread.h> pthread_cond_t cv; pthread_condattr_t cattr; int ret; /* initialize a condition variable to its default value */ ret = pthread_cond_init(&cv, NULL); /* initialize a condition variable */ ret = pthread_cond_init(&cv, &cattr);
cattr を NULL に設定するのは、デフォルト条件変数属性オブジェクトのアドレスを渡すのと同じですが、メモリーのオーバーヘッドがありません。
静的に定義された条件変数をデフォルト属性に初期化するには、マクロ PTHREAD_COND_INITIALIZER を使用します。PTHREAD_COND_INITIALIZER マクロは、NULL 属性を指定して pthread_cond_init() を動的に割り当てた場合と同じ効果を適用します。エラーチェックは行われません。
複数のスレッドで同じ条件変数を同時に初期化または再初期化しないでください。条件変数を再初期化または削除する場合、アプリケーションでその条件変数が現在使用されていないことを確認しなければなりません。
pthread_cond_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:cattr で指定された値が無効です。
EBUSY
説明:その条件変数は現在使用されています。
EAGAIN
説明:必要なリソースが利用できません。
ENOMEM
説明:メモリー不足のため条件変数を初期化できません。
mp が指す相互排他ロックを不可分操作により解放し、 cv が指す条件変数で呼び出しスレッドをブロックするには、pthread_cond_wait(3C) を使用します。
int pthread_cond_wait(pthread_cond_t *restrict cv,pthread_mutex_t *restrict mutex);
#include <pthread.h> pthread_cond_t cv; pthread_mutex_t mp; int ret; /* wait on condition variable */ ret = pthread_cond_wait(&cv, & mp);
ブロックされたスレッドを呼び起こすには、pthread_cond_signal () か pthread_cond_broadcast() を使用します。シグナルの割り込みによっても呼び起こすことができます。
pthread_cond_wait() が終了しても、条件変数に関連付けられた条件の値が変化したと判断することはできません。条件は再評価する必要があります。
pthread_cond_wait() が戻るときは、たとえエラーを戻したときでも、常に mutex は呼び出しスレッドがロックし保持している状態にあります。
pthread_cond_wait は、指定の条件変数にシグナルが送られてくるまでブロック状態になります。pthread_cond_wait は、不可分操作により、対応する mutex ロックを解除してからブロック状態に入り、ブロック状態から戻る前にもう一度不可分操作によりロックを獲得します。
通常の用法は次のとおりです。mutex ロックの保護下で条件式を評価します。条件式が偽のとき、スレッドは条件変数でブロック状態に入ります。別のスレッドが条件の値を変更すると、条件変数にシグナルが送られます。この変更によって、その条件変数で待ち状態となっていた少なくとも 1 つのスレッドがブロック解除され、もう一度 mutex を獲得しようとします。
pthread_cond_wait() の位置から実行を続行する前に、待機の条件を再テストする必要があります。呼び起こされたスレッドが mutex を再度獲得して pthread_cond_wait() から戻る前に、条件が変更される可能性があります。また、待機スレッドが誤って呼び起こされる可能性があります。条件チェックを while() ループに入れ、そこで pthread_cond_wait() を呼び出すようにすることをお勧めします。
pthread_mutex_lock(); while(condition_is_false) pthread_cond_wait(); pthread_mutex_unlock();
スケジューリングポリシーは、ブロックされたスレッドがどのように呼び起こされるかを決定します。デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドが呼び起こされる順序を指定していません。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、スレッドは優先順位に従って呼び起こされます。
pthread_cond_wait() は取り消しポイントです。保留状態になっている取り消しがあって、呼び出しスレッドが取り消しを有効 (使用可能) にしている場合、そのスレッドは終了し、ロックしている間にクリーンアップハンドラの実行を開始します。
pthread_cond_wait() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:cv または mp で指定された値が無効です。
cv が指す条件変数でブロックされている 1 つのスレッドのブロックを解除するには、pthread_cond_signal(3C) を使用します。
int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* one condition variable is signaled */ ret = pthread_cond_signal(&cv);
関連付けられた条件は、シグナルを送ろうとしている条件変数と同じ mutex ロックを獲得した状態で変更します。そうしないと、これらの条件変数が評価されてから pthread_cond_wait() でブロック状態に入るまでの間に条件変数が変更される可能性があり、その場合 pthread_cond_wait は永久に待ち続けることになります。
スケジューリングポリシーは、ブロックされたスレッドがどのように呼び起こされるかを決定します。デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドが呼び起こされる順序を指定していません。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、スレッドは優先順位に従って呼び起こされます。
条件変数でブロックされるスレッドがない場合、pthread_cond_signal() を呼び出しても何も起こりません。
pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count() { pthread_mutex_lock(&count_lock); while (count == 0) pthread_cond_wait(&count_nonzero, &count_lock); count = count - 1; pthread_mutex_unlock(&count_lock); } increment_count() { pthread_mutex_lock(&count_lock); if (count == 0) pthread_cond_signal(&count_nonzero); count = count + 1; pthread_mutex_unlock(&count_lock); }
pthread_cond_signal() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:cv が指すアドレスが正しくありません。
例 4–8 は、pthread_cond_wait() と pthread_cond_signal() の使用例です。
pthread_cond_timedwait(3C)は、abstime で指定された時刻を過ぎるとブロック状態を解除する点を除いて、pthread_cond_wait() と同じ動作をします。()
int pthread_cond_timedwait(pthread_cond_t *restrict cv, pthread_mutex_t *restrict mp, const struct timespec *restrict abstime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t abstime; int ret; /* wait on condition variable */ ret = pthread_cond_timedwait(&cv, & mp, &abstime);
pthread_cond_timedwait() は、たとえエラーを返す場合でも、常に呼び出しスレッドが mutex ロックし、所有した状態で終了します。()
pthread_cond_timedwait() のブロック状態が解除されるのは、条件変数にシグナルが送られてきたときか、一番最後の引数で指定した時刻を過ぎたときです。
pthread_cond_timedwait() は取り消しポイントでもあります。
pthread_timestruc_t to; pthread_mutex_t m; pthread_cond_t c; ... pthread_mutex_lock(&m); clock_gettime(CLOCK_REALTIME, &to); to.tv_sec += TIMEOUT; while (cond == FALSE) { err = pthread_cond_timedwait(&c, &m, &to); if (err == ETIMEDOUT) { /* timeout, do something */ break; } } pthread_mutex_unlock(&m);
pthread_cond_timedwait() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。
EINVAL
説明:cv、mp、または abstime が不当なアドレスを指しています。
EINVAL
説明:同じ条件変数への並行した pthread_cond_timedwait() 操作に対して異なる mutex が指定されました。
ETIMEDOUT
説明:abstime で指定された時刻を過ぎています。
EPERM
説明:呼び出し時に、mutex が現在のスレッドによって所有されていませんでした。
時間切れの指定は時刻で行うため、時間切れ時刻を再計算する必要がなく、効率的に条件を再評価できます。例 4–9 を参照してください。
pthread_cond_reltimedwait_np(3C) の使用方法は、1 点を除いて、pthread_cond_timedwait() の使用方法と同じです。 pthread_cond_reltimedwait_np() の場合は、最後の引数として、未来の絶対日時ではなく相対時間間隔を指定します。
int pthread_cond_reltimedwait_np(pthread_cond_t *cv, pthread_mutex_t *mp, const struct timespec *reltime);
#include <pthread.h> #include <time.h> pthread_cond_t cv; pthread_mutex_t mp; timestruct_t reltime; int ret; /* wait on condition variable */ ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);
pthread_cond_reltimedwait_np() は、たとえエラーを返す場合でも、常に呼び出しスレッドが mutex ロックし、所有した状態で終了します。()pthread_cond_reltimedwait_np() 関数は、条件のシグナルを受け取るか、最後の引数に指定されている時間間隔が経過するまで、ブロックします。
pthread_cond_reltimedwait_np() は、取り消しポイントでもあります。
pthread_cond_reltimedwait_np() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。
EINVAL
説明:reltime で指定された値が無効です。
ETIMEDOUT
説明:reltime に指定されている時間間隔が経過しました。
pthread_cond_wait() で指定された cv が指す条件変数でブロックされている、すべてのスレッドのブロックを解除するには、pthread_cond_broadcast(3C) を使用します。
int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* all condition variables are signaled */ ret = pthread_cond_broadcast(&cv);
スレッドがブロックされていない条件変数に対して pthread_cond_broadcast() を実行しても無視されます。
pthread_cond_broadcast() は、条件変数でブロックされていたすべてのスレッドにもう一度相互排他ロックを争奪させるので、慎重に使用してください。()たとえば、pthread_cond_broadcast() を使用して、可変量のリソースを、解放時にスレッド間で争奪させることができます。例 4–10 を参照してください。
pthread_mutex_t rsrc_lock; pthread_cond_t rsrc_add; unsigned int resources; get_resources(int amount) { pthread_mutex_lock(&rsrc_lock); while (resources < amount) { pthread_cond_wait(&rsrc_add, &rsrc_lock); } resources -= amount; pthread_mutex_unlock(&rsrc_lock); } add_resources(int amount) { pthread_mutex_lock(&rsrc_lock); resources += amount; pthread_cond_broadcast(&rsrc_add); pthread_mutex_unlock(&rsrc_lock); }
add_resources() の部分に注目してください。相互排他ロックの範囲内では、resources の更新と pthread_cond_broadcast() の呼び出しのどちらを先に行なってもかまいません。
関連付けられた条件は、シグナルを送ろうとしている条件変数と同じ mutex ロックを獲得した状態で変更します。そうしないと、これらの条件変数が評価されてから pthread_cond_wait() でブロック状態に入るまでの間に条件変数が変更される可能性があり、その場合 pthread_cond_wait は永久に待ち続けることになります。
pthread_cond_broadcast() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:cv が指すアドレスが正しくありません。
cv が指す条件変数に関連付けられた状態を削除するには、pthread_cond_destroy(3C) を使用します。
int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h> pthread_cond_t cv; int ret; /* Condition variable is destroyed */ ret = pthread_cond_destroy(&cv);
条件変数の記憶領域は解放されません。
pthread_cond_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:cv で指定された値が無効です。
pthread_cond_signal() または pthread_cond_broadcast() を呼び出すとき、スレッドが条件変数に関連する相互排他ロックを保持していないと「呼び起こし忘れ」(lost wake-up) という問題が生じることがあります。
「呼び起こし忘れ」は次のすべての条件が揃った場合に発生します。
スレッドが pthread_cond_signal() または pthread_cond_broadcast() を呼び出す
待機しているスレッドが存在しない
シグナルは無効になり、失われます
この問題は、条件に関連付けられた相互排他ロックを保持しないでテスト対象の条件が変更された場合に限り発生します。関連付けられた mutex を保持した状態でテスト対象の条件を変更した場合には、pthread_cond_signal() と pthread_cond_broadcast() を呼び出すときにそれらがその mutex を保持しているかどうかは影響しません。
「生産者 / 消費者」問題は、並行プログラミングに関する問題の中でも一般によく知られているものの 1 つです。この問題は次のように定式化されます。サイズが有限の 1 個のバッファーと 2 種類のスレッドが存在します。一方のスレッドを生産者、もう一方のスレッドを消費者と呼びます。
生産者は、バッファーに空きができるまでデータを入れることができません。消費者は、生産者がバッファーに何か書き込むまで、このバッファーからデータを取り出すことができません。
特定の条件のシグナルを待つスレッドの待ち行列を条件変数で表すことにします。
例 4–11には、こうした待ち行列を 2 つ使用しています。1 つは less で、バッファー内の未使用スロットを待つ生産者のための待ち行列です。もう 1 つは more で、情報が格納されたバッファースロットを待つ消費者のための待ち行列です。また、バッファーが同時に複数のスレッドによってアクセスされないようにするために、相互排他ロック (mutex ロック) も使用しています。
typedef struct { char buf[BSIZE]; int occupied; int nextin; int nextout; pthread_mutex_t mutex; pthread_cond_t more; pthread_cond_t less; } buffer_t; buffer_t buffer;
例 4–12 では、最初に、生産者のスレッドが mutex を獲得し、バッファーデータ構造 (buffer) を保護します。この生産者スレッドは、次に、生成された項目のための空きがあることを確認します。空きがない場合は、pthread_cond_wait () を呼び出します。pthread_cond_wait() は、「バッファー内に空きがある」を表す条件 less にシグナルが送られてくるのを待つスレッドの待ち行列に入ります。
同時に、pthread_cond_wait() の呼び出しによって、スレッドは mutex のロックを解除します。生産者スレッドは、条件が真になって消費者スレッドがシグナルを送ってくれるのを待ちます。例 4–12 を参照してください。条件にシグナルが送られてくると、less を待っている一番目のスレッドが呼び起こされます。しかし、そのスレッドは pthread_cond_wait() が戻る前に、mutex ロックを再び獲得する必要があります。
これにより、バッファーデータ構造への相互排他アクセスが保証されます。その後、生産者スレッドは、バッファーに本当に空きがあるか確認する必要があります。空きがあれば、最初の未使用スロットにデータを入れます。
このとき、バッファーにデータが入れられるのを消費者スレッドが待っている可能性があります。そのスレッドは、条件変数 more で待ち状態となっています。生産者スレッドはバッファーにデータを入れると、pthread_cond_signal() を呼び出して、待ち状態の最初の消費者を呼び起こします待ち状態の消費者がいないときは、この呼び出しは無視されます。
最後に、生産者スレッドは mutex ロックを解除して、ほかのスレッドがバッファーデータ構造を操作できるようにします。
void producer(buffer_t *b, char item) { pthread_mutex_lock(&b->mutex); while (b->occupied >= BSIZE) pthread_cond_wait(&b->less, &b->mutex); assert(b->occupied < BSIZE); b->buf[b->nextin++] = item; b->nextin %= BSIZE; b->occupied++; /* now: either b->occupied < BSIZE and b->nextin is the index of the next empty slot in the buffer, or b->occupied == BSIZE and b->nextin is the index of the next (occupied) slot that will be emptied by a consumer (such as b->nextin == b->nextout) */ pthread_cond_signal(&b->more); pthread_mutex_unlock(&b->mutex); }
assert() 文の用法に注意してください。NDEBUG を定義してコードをコンパイルした場合を除き、assert() はその引数が真 (0 以外) として評価された場合は何も行いません。その引数が偽 (0) として評価された場合、プログラムは終了します。こうしたアサーションはマルチスレッドプログラムで特に役立ちます。assert() は、アサーションが失敗すると実行時に発生した問題をただちに指摘します。assert() の効果はほかにもあり、役立つコメントを提供します。
/* 現在の状態: ... で始まるコメント部分も、assert で表現した方がよいかもしれません。しかし、論理式で表現するには複雑すぎるので、ここでは文章で表現しています。
assert の部分もコメント部分も、不変式になっています。これらの不変式は、プログラムの実行により偽の値に変更されない論理式ですが、次のような例外があります。この例外は、あるスレッドが不変式中のプログラム変数を変更している瞬間に起こります。もちろん、assert の論理式は、どのスレッドがいつ実行した場合でも常に真であるべきです。
不変式は非常に重要な手法です。プログラムテキストとして明示的に表現しなくても、プログラムを分析するときは不変式に置き換えて問題を考えることが大切です。
上記の生産者コード内のコメントで表現された不変式は、スレッドがそのコメントを含むコード部分を実行中には常に真となります。しかし、それを mutex_unlock() のすぐ後ろに移動すると、必ずしも常に真とはなりません。assert() のすぐ後ろに移動した場合は、真となります。
このため、この不変式は常に真となる特性を持っていますが、次のような例外があります。この例外は、生産者または消費者がバッファーの状態を変更しているときに起こります。スレッドは mutex の保護下でバッファーを操作しているとき、この不変式の値を一時的に偽にしてもかまいません。しかし、処理が完了したら不変式の値を再び真に戻さなければなりません。
例 4–13 に、消費者のコードを示します。論理フローは生産者の場合と対称的です。
char consumer(buffer_t *b) { char item; pthread_mutex_lock(&b->mutex); while(b->occupied <= 0) pthread_cond_wait(&b->more, &b->mutex); assert(b->occupied > 0); item = b->buf[b->nextout++]; b->nextout %= BSIZE; b->occupied--; /* now: either b->occupied > 0 and b->nextout is the index of the next occupied slot in the buffer, or b->occupied == 0 and b->nextout is the index of the next (empty) slot that will be filled by a producer (such as b->nextout == b->nextin) */ pthread_cond_signal(&b->less); pthread_mutex_unlock(&b->mutex); return(item); }
セマフォーは、E. W. ダイクストラ (Dijkstra) が 1960 年代の終わりごろに考案したプログラミング手法です。ダイクストラのセマフォーモデルは、鉄道線路の運行をモデル化したものです。一度に一本の列車しか走れない単線の鉄道線路を思い浮かべてください。
この鉄道線路上の運行の同期を取るのがセマフォー (腕木信号機) です。列車は単線区間に入るとき、セマフォーの状態が進行許可状態になるのを待たなければなりません。列車が単線区間に入るとセマフォーの状態は、ほかの列車が単線区間に入るのを禁止する状態に変化します。単線区間から出る列車は、セマフォーの状態を進行許可状態に戻して、ほかの列車が単線区間に入ることができるようにしなければなりません。
コンピュータ内のセマフォーは、単一の整数で表現されます。スレッドは進行が許可されるのを待ち、その後進行したことを知らせるためにセマフォーに対して P 操作を実行します。
スレッドは、セマフォーの値が正になるのを待たなければなりません。その後、値から 1 を引いて、セマフォーの値を変更します。処理を完了したセマフォーは、V 操作を実行します。この操作は 1 を加えることでセマフォーの値を変更します。これらの操作は不可分的に行う必要があります。これらの操作を分割して、次の操作までの間に、セマフォーに対してその他のアクションが実行されるようなことがあってはなりません。P 操作では、1 を引く直前のセマフォーの値が正でなければなりません。結果的に、引いた後の値が負にならないことと、その値が引く前の値よりも 1 だけ小さいことが保証されます。
P 操作と V 操作のどちらの演算操作でも干渉が生じないようにしなければなりません。たとえば、同じセマフォーに対して 2 つの V 操作が同時に行われた場合、そのセマフォーの新しい値は最初よりも 2 だけ大きくなっていなければなりません。
ダイクストラがオランダ人だったため、P と V の記号的な意味は、現在ではほとんど忘れられています。参考までに、 P はオランダ語の「prolagen」という単語を表します。その語源は「proberen te verlagen」で、「小さくする」という意味です。また、V は「verhogen」を表し、「大きくする」という意味です。このことは、ダイクストラのテクニカルノート『EWD 74』で説明されています。
sem_wait(3RT) と sem_post(3RT) は、ダイクストラの P 操作と V 操作にそれぞれ対応しています。sem_trywait(3RT) は、P 操作の条件付きの形式です。この関数は、呼び出しスレッドがセマフォーの値を差し引くために待たなければならない場合は、ただちに 0 以外の値を返します。
セマフォーは、大きく 2 つに分類できます。1 つは 2 進型セマフォーで、0 および 1 以外の値はとりません。もう 1 つは計数型セマフォーで、0 以上の任意の値をとります。2 進型セマフォーは、論理的に mutex と同じです。
必ずしも必須要件ではありませんが、mutex のロックは、ロックを保持しているスレッドがそのロックを解放するべきです。ただし、セマフォーには「スレッドがセマフォーを保持している」という概念がないため、任意のスレッドが V 操作 (sem_post(3RT)) を実行できます。
計数型セマフォーは、mutex とともに使用される条件変数と同等の能力があります。多くの場合、条件変数よりも計数型セマフォーを使用した方がコードが簡素化されます。例 4–14、例 4–15、例 4–16 を参照してください。
しかし、mutex を条件変数といっしょに使用した場合は、暗黙のくくりが存在します。このくくりは、プログラム内で保護されている部分を明確に区別します。この動作はセマフォーに限定されたものではなく、「並行プログラミングにおける go to」と呼ばれることがあります。セマフォーは強力ですが、構造化されないあいまいな方法で使用してしまいがちです。
POSIX セマフォーは、名前付きの場合と名前なしの場合があります。名前なしセマフォーは、プロセスメモリー内で割り当てられ、初期化されます。名前なしセマフォーは、割り当てと初期化の状態によっては、複数のプロセスで使用できます。名前なしセマフォーは、fork() から継承された専用セマフォーであるか、またはその割り当てとマッピングが行われた通常ファイルのアクセス保護によって保護されたセマフォーです。
名前付きセマフォーはプロセス間で共有されるセマフォーに似ていますが、pshared 値ではなくパス名で参照される点が異なります。名前付きセマフォーは、複数のプロセスによる共有が可能です。名前付きセマフォーは、固有のユーザー ID、グループ ID、および保護モードを持ちます。
sem_open、sem_getvalue、sem_close、sem_unlink の各関数が、名前付きセマフォーを開く、取得する、閉じる、削除するのにそれぞれ使用できます。sem_open では、ファイルシステムの名前空間で名前が定義されたセマフォーを生成できます。
名前付きセマフォーの詳細は、sem_open、sem_getvalue、sem_close、sem_unlink のマニュアルページを参照してください。
セマフォーの概念は、0 以上の整数カウントです。通常は、リソースに対するアクセスの調整をはかる目的で、次のように使用されます。最初に、使用可能なリソースの数をセマフォーに初期設定します。スレッドは、リソースが追加されると不可分操作的にカウントを 1 加算し、リソースが削除されると不可分操作的に 1 減算します。
これ以上リソースが存在しなくなると、セマフォーカウントは 0 になります。この場合、スレッドがカウントを 1 減らそうとすると、カウントが 0 より大きくなるまでブロックされます。
表 4–6 セマフォーに関するルーチン
操作 |
参照先 |
---|---|
セマフォーの初期化 | |
セマフォーの加算 | |
セマフォーの値によるブロック | |
セマフォーの減算 | |
セマフォーの削除 |
セマフォーは、その獲得と解放を同じスレッドで行う必要がないため、シグナルハンドラで行われているような非同期のイベント通知に利用できます。また、セマフォー自身が状態を持っているため、条件変数を使用する場合と違って相互排他ロックを獲得しなくても非同期で使用できます。ただし、セマフォーは相互排他ロックほど効率的ではありません。
スケジューリングポリシーは、ブロックされたスレッドがどのように呼び起こされるかを決定します。デフォルトスケジューリングポリシー SCHED_OTHER は、スレッドが呼び起こされる順序を指定していません。SCHED_FIFO および SCHED_RR リアルタイムスケジューリングポリシーの下では、スレッドは優先順位に従って呼び起こされます。
セマフォーは、使用する前に初期化されている必要がありますが、属性はありません。
sem が指す名前なしセマフォー変数を value の値に初期化するには、sem_init(3RT) を使用します。
int sem_init(sem_t *sem, int pshared, unsigned int value);
#include <semaphore.h> sem_t sem; int pshared; int ret; int value; /* initialize a private semaphore */ pshared = 0; value = 1; ret = sem_init(&sem, pshared, value);
pshared の値が 0 なら、そのセマフォーはプロセス間で共有できません。pshared の値が 0 以外なら、そのセマフォーはプロセス間で共有できます。
複数のスレッドから同じセマフォーを初期化してはいけません。
一度初期化したセマフォーは、ほかのスレッドが使用している可能性があるので、再初期化してはいけません。
pshared が 0 の場合、セマフォーを使用できるのは、このプロセス内のスレッドだけです。
#include <semaphore.h> sem_t sem; int ret; int count = 4; /* to be used within this process only */ ret = sem_init(&sem, 0, count);
pshared が 0 以外の場合、セマフォーは複数のプロセスで共有可能です。
#include <semaphore.h> sem_t sem; int ret; int count = 4; /* to be shared among processes */ ret = sem_init(&sem, 1, count);
sem_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:value の値が SEM_VALUE_MAX を超えています。
ENOSPC
説明:そのセマフォーを初期化するのに必要なリソースが使い果たされています。セマフォーの制限 SEM_NSEMS_MAX に達しています。
EPERM
説明:そのセマフォーを初期化するのに必要な特権をそのプロセスがもっていません。
sem が指すセマフォーを不可分的に加算するには、sem_post(3RT) を使用します。
int sem_post(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_post(&sem); /* semaphore is posted */
そのセマフォーでブロックされているスレッドがある場合は、そのスレッドのうちの 1 つのスレッドがブロック解除されます
sem_post() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:sem が指すアドレスが正しくありません。
sem が指すセマフォーカウントが 0 より大きくなるまで呼び出しスレッドをブロックし、0 より大きくなったら不可分的にカウントを減らすには、sem_wait(3RT) を使用します。
int sem_wait(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_wait(&sem); /* wait for semaphore */
sem_wait() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:sem が指すアドレスが正しくありません。
EINTR
説明:この関数にシグナルが割り込みを行いました。
sem が指すセマフォー内のカウントが 0 より大きいときにこの値を不可分的に減らすには、sem_trywait(3RT) を使用します。
int sem_trywait(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_trywait(&sem); /* try to wait for semaphore*/
この関数はブロックしない点を除いて、sem_wait() と同じ働きをします。つまり、失敗した場合にはただちに終了します。()
sem_trywait() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:sem が指すアドレスが正しくありません。
EINTR
説明:この関数にシグナルが割り込みを行いました。
EAGAIN
説明:そのセマフォーはすでにロックされているので、sem_trywait() でただちにロックできません。
sem が指す名前なしセマフォーに関連付けられた状態を削除するには、sem_destroy(3RT) を使用します。
int sem_destroy(sem_t *sem);
#include <semaphore.h> sem_t sem; int ret; ret = sem_destroy(&sem); /* the semaphore is destroyed */
セマフォーの記憶領域は解放されません。
sem_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。
EINVAL
説明:sem が指すアドレスが正しくありません。
例 4–14 のデータ構造は、例 4–11 の条件変数の例で使用されている構造とよく似ています。2 つのセマフォーは、いっぱいになったバッファー数と空のバッファー数をそれぞれ表します。これらのセマフォーは、バッファーが空になるまで生産者を待たせ、バッファーがいっぱいになるまで消費者を待たせます。
typedef struct { char buf[BSIZE]; sem_t occupied; sem_t empty; int nextin; int nextout; sem_t pmut; sem_t cmut; } buffer_t; buffer_t buffer; sem_init(&buffer.occupied, 0, 0); sem_init(&buffer.empty,0, BSIZE); sem_init(&buffer.pmut, 0, 1); sem_init(&buffer.cmut, 0, 1); buffer.nextin = buffer.nextout = 0;
もう一組の 2 進型セマフォーを mutex として使用しています。このセマフォーは、複数の生産者が複数の空バッファースロットを使用するときと、複数の消費者が複数のいっぱいになったバッファースロットを使用するときのバッファーへのアクセスを制御します。本来このような場合では mutex を使用すべきですが、セマフォーの使用例を示すために特に使用しています。
void producer(buffer_t *b, char item) { sem_wait(&b->empty); sem_wait(&b->pmut); b->buf[b->nextin] = item; b->nextin++; b->nextin %= BSIZE; sem_post(&b->pmut); sem_post(&b->occupied); }
char consumer(buffer_t *b) { char item; sem_wait(&b->occupied); sem_wait(&b->cmut); item = b->buf[b->nextout]; b->nextout++; b->nextout %= BSIZE; sem_post(&b->cmut); sem_post(&b->empty); return(item); }
読み取り/書き込みロックによって、保護された共有リソースに対する並行する複数の読み取りと排他的な書き込みが可能になります。読み取り/書き込みロックは単一の実体で、読み取りモードまたは書き込みモードでロック可能です。リソースを変更するには、スレッドがまず排他書き込みロックを獲得する必要があります。すべての読み取りロックが開放されない限り、排他書き込みロックは許可されません。
データベースアクセスは、読み取り/書き込みロックと同期させることができます。読み取り操作によってレコードの情報が変更されることはないので、読み取り/書き込みロックではデータベースのレコードを並行して読み取ることができます。データベースを更新するときは、書き込み操作は排他的書き込みロックを獲得する必要があります。
デフォルトの読み取り/書き込みロック属性を変更するときに、属性オブジェクトを宣言および初期化することができます。多くの場合、読み取り/書き込みロック属性は、アプリケーションの開始位置の 1 か所に設定してあります。アプリケーションの開始位置に設定すると、この属性の検出および修正が容易になります。ここで説明した読み取り/書き込みロック属性を操作する関数を、次の表に示します。
表 4–7 読み取り/書き込みロック属性のルーチン
操作 |
参照先 |
---|---|
読み取り/書き込みロック属性の初期化 | |
読み取り/書き込みロック属性の削除 | |
読み取り/書き込みロック属性の設定 | |
読み取り/書き込みロック属性の取得 |
pthread_rwlockattr_init(3C) は、読み取り/書き込みロック属性オブジェクト attr を、実装によって定義されたすべての属性をデフォルト値に初期化します。
#include <pthread.h> int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
pthread_rwlockattr_init を呼び出すときに、初期化済みの読み取り/書き込みロック属性オブジェクトを指定した場合、結果は保証されません。読み取り/書き込みロック属性オブジェクトによって初期化された読み取り/書き込みロックは、属性オブジェクトに影響を与えるどんな関数(削除を含む)の影響も受けないためです。
pthread_rwlockattr_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
ENOMEM
説明:読み取り/書き込み属性オブジェクトを初期化するためのメモリーが足りません。
pthread_rwlockattr_destroy(3C) は、読み取り/書き込みロック属性を削除します。
#include <pthread.h> int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
削除したオブジェクトを、pthread_rwlockattr_init() を呼び出して再び初期化する前に使った場合、その結果は未定義です。実装によっては、pthread_rwlockattr_destroy() により、attr が参照するオブジェクトに不正な値が設定される場合もあります。
pthread_rwlockattr_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:attr で指定された値が無効です。
pthread_rwlockattr_setpshared(3C) は、プロセス共通の読み取り/書き込みロック属性を設定します。
#include <pthread.h> int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
PTHREAD_PROCESS_SHARED
説明:読み取り/書き込みロックが割り当てられているメモリーにアクセスできるすべてのスレッドに、読み取り/書き込みロックの操作を許可します。読み取り/書き込みロックが複数のプロセスによって共有されているメモリーに割り当てられている場合にも、ロックの操作を許可します。
PTHREAD_PROCESS_PRIVATE
説明:読み取り/書き込みロックを操作できるのは、そのロックを初期化したスレッドと同じプロセス内で作成されたスレッドだけです。異なるプロセスのスレッドから読み取り/書き込みロックを操作しようとした場合、その結果は未定義です。プロセス共有の属性のデフォルト値は、PTHREAD_PROCESS_PRIVATE です。
pthread_rwlockattr_setpshared() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:attr または pshared が示す値は無効です。
pthread_rwlockattr_getpshared(3C) は、プロセス共有の読み取り/書き込みロック属性を取得します。
#include <pthread.h> int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
pthread_rwlockattr_getpshared() は、attr が参照する初期化済みの属性オブジェクトから、プロセス共有の属性の値を取得します。
pthread_rwlockattr_getpshared() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:attr または pshared が示す値は無効です。
読み取り/書き込みロックの属性を設定したら、読み取り/書き込みロックそのものを初期化します。次の関数を使って、読み取り/書き込みロックを初期化または削除したり、ロックまたはロック解除したり、ロックを試みたりできます。ここで説明した読み取り/書き込みロック属性を操作する関数を、次の表に示します。
表 4–8 読み取り/書き込みロック属性のルーチン
操作 |
参照先 |
---|---|
読み取り/書き込みロックの初期化 | |
読み取り/書き込みロックの読み取りロック | |
非ブロック読み取り/書き込みロックの読み取りロック | |
読み取り/書き込みロックの書き込みロック | |
非ブロック読み取り/書き込みロックの書き込みロック | |
読み取り/書き込みロックの解除 | |
読み取り/書き込みロックの削除 |
attr が参照する属性を使用して rwlock が参照する読み取り/書き込みロックを初期化するには、pthread_rwlock_init(3C) を使用します。
#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
attr が NULL の場合、デフォルトの読み取り/書き込みロック属性が使われます。この場合の結果は、デフォルトの読み取り/書き込みロック属性オブジェクトのアドレスを渡す場合と同じです。いったん初期化したロックは、繰り返して使用するために再び初期化する必要はありません。初期化が成功すると、読み取り/書き込みロックは初期化され、ロックが解除された状態になります。初期化済みの読み取り/書き込みロックを指定して、pthread_rwlock_init() を呼び出した場合、その結果は不定です。最初に初期化しないで読み取り/書き込みロックを使用した場合も、その結果は不定です。
デフォルトの読み取り/書き込みロック属性を使用するのであれば、PTHREAD_RWLOCK_INITIALIZER というマクロで、静的に割り当てられている読み取り/書き込みロックを初期化できます。この場合の結果は、パラメータ attr に NULL を指定して pthread_rwlock_init() を呼び出し、動的に初期化したときと同じです。ただし、エラーチェックが実行されません。
pthread_rwlock_init () は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
pthread_rwlock_init() が正常に終了しなかった場合、rwlock は初期化されず、rwlock の内容は未定義です。
EINVAL
説明:attr または rwlock が示す値は無効です。
pthread_rwlock_rdlock(3C) は、rwlock が参照する読み取り/書き込みロックに読み取りロックを適用します。
#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
呼び出しスレッドは、書き込みがロックを保持せず、読み取り/書き込みロックでブロックされている書き込みもない場合は、読み取りロックを獲得します。書き込みがロックを保持せず、ロック待ちの書き込みがある場合は、呼び出しスレッドが読み取りロックを獲得するかどうかは不定です。書き込みが読み取り/書き込みロックを保持している場合は、呼び出しスレッドは読み取りロックを獲得しません。読み取りロックが獲得されない場合、呼び出しスレッドはブロックされます。つまり、呼び出しスレッドは、スレッドがロックを獲得するまで、pthread_rwlock_rdlock() から戻り値を取得しません。呼び出し時に、呼び出しスレッドが rwlock に書き込みロックを保持する場合、その結果は不定です。
書き込み側がいつまでもロックを獲得できない事態を避けるために、書き込みが読み取りに優先するように実装することが許されています。Solaris スレッドの実装では、書き込みが読み取りに優先します。
スレッドは、rwlock に複数の並行的な読み取りロックを保持できます。つまり、pthread_rwlock_rdlock() の呼び出しが n 回成功します。この場合、スレッドは、pthread_rwlock_unlock() を n 回呼び出して、同数の読み取りロック解除を行わなければなりません。
pthread_rwlock_rdlock() が、初期化されていない読み取り/書き込みロックに対して呼び出された場合、その結果は不定です。
スレッドシグナルハンドラは、読み取り/書き込みロックを待っているスレッドに送信されたシグナルを処理します。スレッドはシグナルハンドラから戻ると、見かけ上割り込みがなかった場合と同様に、読み取りのための読み取り/書き込みロック待ちを再開します。
pthread_rwlock_rdlock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:attr または rwlock が示す値は無効です。
pthread_rwlock_timedrdlock(3C) 関数は、pthread_rwlock_rdlock() 関数と同様に、rwlock が参照する読み取り/書き込みロックに読み取りロックを適用します。
#include <pthread.h> #include <time.h> int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
ほかのスレッドによるロック解除を待たなければロックを獲得できない場合、この待機は、指定されたタイムアウトの期限が切れると終了します。タイムアウトの期限が切れるのは、abs_timeout で指定された絶対時間 (CLOCK_REALTIME クロックで測定される) が経過した場合 (つまり、そのクロックの値が abs_timeout に等しくなるか、超えた場合)、または呼び出し時に abs_timeout で指定された絶対時間がすでに過ぎている場合です。
タイムアウトの解像度は、CLOCK_REALTIME クロックの解像度です。timespec データ型は、<time.h> ヘッダーで定義されています。ロックをただちに獲得できる場合は、この関数がタイムアウトで失敗することはありません。ロックをただちに獲得できた場合は、タイムアウトパラメータの妥当性を検査する必要はありません。
pthread_rwlock_timedrdlock() の呼び出しによって読み取り/書き込みロックでブロックされているスレッドに、シグナルハンドラを実行させるシグナルが送信された場合、このスレッドはシグナルハンドラから復帰すると、中断がなかったかのようにロックの待機を再開します。
呼び出しスレッドは、呼び出し時に rwlock に書き込みロックを保持しているとデッドロックに陥る可能性があります。
pthread_rwlock_reltimedrdlock_np() 関数は、タイムアウトが相対時間間隔として指定される点を除いて、pthread_rwlock_timedrdlock() 関数と同じ動作をします。
正常に終了して、rwlock が参照する読み取り/書き込みロックオブジェクトに対する書き込みロックが獲得された場合、0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
ETIMEDOUT
説明:指定されたタイムアウトの期限が切れる前にロックを獲得できませんでした。
EAGAIN
説明:ロックの読み取りロックの最大数を超えるため、読み取りロックを獲得できません。
EDEADLK
説明:呼び出しスレッドは、rwlock をすでに保持しています。
EINVAL
説明:rwlock で指定された値が初期化された読み取り/書き込みロックオブジェクトを表していないか、タイムアウトのナノ秒の値が 0 未満または 10 億以上です。
pthread_rwlock_tryrdlock(3C) は、pthread_rwlock_rdlock() と同様に読み取りロックを適用します。ただし、いずれかのスレッドが rwlock に書き込みロックを保持しているか、rwlock で書き込みスレッドがブロックされている場合、この関数は失敗します。
#include <pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock() は、rwlock が参照する読み取り/書き込みロックオブジェクトに対する読み取りロックが獲得された場合、0 を返します。ロックが獲得されなかった場合は、エラーを示すエラー番号が返されます。
EBUSY
説明:書き込みが読み取り/書き込みロックを保持しているか、読み取り/書き込みロックで書き込みスレッドがブロックされているため、読み取りのための読み取り/書き込みロックを獲得できません。
pthread_rwlock_wrlock(3C) は、rwlock が参照する読み取り/書き込みロックに書き込みロックを適用します。
#include <pthread.h> int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
呼び出しスレッドは、ほかの読み取りスレッドまたは書き込みスレッドが rwlock という読み取り/書き込みロックを保持していない場合、書き込みロックを獲得します。それ以外の場合、呼び出しスレッドはブロックされます。つまり、呼び出しスレッドは、スレッドがロックを獲得するまで、pthread_rwlock_wrlock() から戻り値を取得しません。呼び出し時に、呼び出しスレッドが読み取り/書き込みロックを保持している場合 (読み取りロックと書き込みロックのどちらでも) の結果は不定です。
書き込み側がいつまでもロックを獲得できない事態を避けるために、書き込みが読み取りに優先するように実装することが許されています。Solaris スレッドの実装では、書き込みが読み取りに優先します。
pthread_rwlock_wrlock() が、初期化されていない読み取り/書き込みロックに対して呼び出された場合、その結果は不定です。
スレッドシグナルハンドラは、書き込みのための読み取り/書き込みロックを待っているスレッドに送信されたシグナルを処理します。スレッドはシグナルハンドラから戻ると、見かけ上割り込みがなかった場合と同様に、書き込みのための読み取り/書き込みロック待ちを再開します。
pthread_rwlock_wrlock() は、rwlock が参照する読み取り/書き込みロックオブジェクトに対する書き込みロックが獲得された場合、0 を返します。ロックが獲得されなかった場合は、エラーを示すエラー番号が返されます。
pthread_rwlock_trywrlock(3C) は、pthread_rwlock_wrlock() と同様に書き込みロックを適用します。ただし、いずれかのスレッドが現時点で rwlock (読み取り用または書き込み用) を保持している場合、この関数は失敗します。
#include <pthread.h> int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
pthread_rwlock_trywrlock() が、初期化されていない読み取り/書き込みロックに対して呼び出された場合、その結果は不定です。
pthread_rwlock_trywrlock() は、正常に終了して、rwlock が参照する読み取り/書き込みロックに対する書き込みロックが獲得された場合、0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EBUSY
説明:読み取りまたは書き込みのための読み取り/書き込みロックがすでにロックされているので、書き込みのための読み取り/書き込みロックを獲得できません。
pthread_rwlock_timedwrlock(3C) 関数は、pthread_rwlock_wrlock () 関数と同様に、rwlock が参照する読み取り/書き込みロックに書き込みロックを適用しますが、指定された絶対時間までしかロックを適用しようとしません。
#include <pthread.h> #include <time.h> int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
呼び出しスレッドは、ほかの読み取りスレッドまたは書き込みスレッドが rwlock という読み取り/書き込みロックを保持していない場合、書き込みロックを獲得します。ほかのスレッドによるロック解除を待たなければロックを獲得できない場合、この待機は、指定されたタイムアウトの期限が切れると終了します。タイムアウトの期限が切れるのは、abs_timeout で指定された絶対時間 (CLOCK_REALTIME クロックで測定される) が経過した場合 (つまり、そのクロックの値が abs_timeout に等しくなるか、超えた場合)、または呼び出し時に abs_timeout で指定された絶対時間がすでに過ぎている場合です。pthread_rwlock_reltimedwrlock_np() 関数は、タイムアウトが相対時間間隔として指定される点を除いて、pthread_rwlock_timedwrlock() 関数と同じ動作をします。
正常に終了して、rwlock が参照する読み取り/書き込みロックオブジェクトに対する書き込みロックが獲得された場合、0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
ETIMEDOUT
説明:指定されたタイムアウトの期限が切れる前にロックを獲得できませんでした。
EDEADLK
説明:呼び出しスレッドは、rwlock をすでに保持しています。
EINVAL
説明:rwlock で指定された値が初期化された読み取り/書き込みロックオブジェクトを表していないか、タイムアウトのナノ秒の値が 0 未満または 10 億以上です。
pthread_rwlock_unlock(3C) は、rwlock が参照する読み取り/書き込みロックオブジェクトに保持されたロックを解放します。
#include <pthread.h> int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
呼び出しスレッドが rwlock という読み取り/書き込みロック を保持していない場合、その結果は不定です。
pthread_rwlock_unlock() を呼び出して読み取り/書き込みロックオブジェクトから読み取りオブジェクトを解放しても、このロックオブジェクトにほかの読み取りロックが保持されている場合、このロックオブジェクトは読み取りにロックされたままになります。pthread_rwlock_unlock() が、呼び出しスレッドによる最後の読み取りロックを解放すると、呼び出しスレッドはこのオブジェクトの所有者でなくなります。pthread_rwlock_unlock() がこの読み取り/書き込みロックオブジェクトの最後の読み取りロックを解放すると、読み取り/書き込みロックオブジェクトはロックが解除され、所有者のない状態になります。
pthread_rwlock_unlock() を呼び出し、読み取り/書き込みロックオブジェクトから書き込みオブジェクトを解放すると、このロックオブジェクトはロックが解除され、所有者のない状態になります。
pthread_rwlock_unlock() を呼び出した結果として読み取り/書き込みロックオブジェクトがロック解除されたときに、複数のスレッドが書き込みのための読み取り/書き込みロックオブジェクトの獲得を待っている場合は、スケジューリングポリシーを使用して、書き込みのための読み取り/書き込みロックオブジェクトを獲得するスレッドが決定されます。複数のスレッドが読み取りのための読み取り/書き込みロックオブジェクトの獲得を待っている場合も、スケジューリングポリシーを使用して、読み取りのための読み取り/書き込みロックオブジェクトを獲得するスレッドの順番が決定されます。複数のスレッドが読み取りロックと書き込みロック両方のために rwlock にブロックされている場合は、読み取り側と書き込み側のどちらが先にロックを獲得するのかは規定されていません。
pthread_rwlock_unlock() が、初期化されていない読み取り/書き込みロックに対して呼び出された場合、その結果は不定です。
pthread_rwlock_unlock() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
pthread_rwlock_destroy(3C) は、rwlock が参照する読み取り/書き込みロックオブジェクトを削除し、このロックが使用していたすべてのリソースを解放します。
#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t **rwlock);
削除したロックを、pthread_rwlock_init() を呼び出して再び初期化する前に使用した場合、その結果は不定です。実装によっては、pthread_rwlock_destroy() により、rwlock が参照するオブジェクトに不正な値が設定される場合もあります。いずれかのスレッドが rwlock() を保持しているときに pthread_rwlock_destroy を呼び出した場合の結果は不定です。初期化されていない読み取り/書き込みロックを削除しようとした場合に発生する動作も不定です。また、削除された読み取り/書き込みロックオブジェクトは、再度 pthread_rwlock_init() で初期化できます。削除した読み取り/書き込みロックオブジェクトを初期化せずに参照した場合も不定です。
pthread_rwlock_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。
EINVAL
説明:attr または rwlock が示す値は無効です。
タスク全体を続行させるために複数のタスクの完了を待つ必要がある場合は、バリアー同期を使用できます。POSIX スレッドでは、バリアー関数とともに、バリアーと呼ばれる同期オブジェクトを指定します。これらの関数は、バリアー上で同期させるスレッドの数を指定してバリアーを作成し、各スレッドを、タスクの実行後、すべてのスレッドがバリアーに到達するまでバリアーで待機するように設定します。最後のスレッドがバリアーに到達すると、すべてのスレッドが実行を再開します。
バリアー同期の詳細については、「共有メモリー型の並列コンピュータでのループの並列化」を参照してください。
バリアーにリソースを割り当て、その属性を初期化するには、pthread_barrier_init(3C) を使用します。
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *restrict attr, unsigned count);
#include <pthread.h> pthread_barrier_t barrier; pthread_barrierattr_t attr; unsigned count; int ret; ret = pthread_barrier_init(&barrier, &attr, count);
pthread_barrier_init() 関数は、barrier が参照するバリアーを使用するために必要なすべてのリソースを割り当て、attr が参照する属性でバリアーを初期化します。attr が NULL の場合は、デフォルトのバリアー属性が使用されます。この場合の結果は、デフォルトのバリアー属性オブジェクトのアドレスを渡す場合と同じです。count 引数には、pthread_barrier_wait() を呼び出す必要のあるスレッドの数を指定します。それまでは、どのスレッドも呼び出しから正常に復帰できません。count で指定する値は 1 以上にする必要があります。
pthread_barrier_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:count で指定された値が 0 に等しいか、または attr で指定された値が無効です。
EAGAIN
説明:別のバリアーを初期化するために必要なシステムリソースが不足しています。
ENOMEM
説明:メモリー不足のためバリアーを初期化できません。
EBUSY
説明:バリアーが別のスレッドによって使用されている間 (たとえば、pthread_barrier_wait() の呼び出しで使用されている間) にそのバリアーの削除の試行が検出されました。
指定したバリアーでスレッドの同期をとるには、pthread_barrier_wait(3C) を使用します。呼び出しスレッドは、必要な数のスレッドがこのバリアーを指定して pthread_barrier_wait() を呼び出すまでブロックされます。スレッドの数は、pthread_barrier_init() 関数で指定されます。
必要な数のスレッドがこのバリアーを指定して pthread_barrier_wait() を呼び出すと、1 つの不定のスレッドに定数 PTHREAD_BARRIER_SERIAL_THREAD が返され、残りの各スレッドには 0 が返されます。バリアーは次に、このバリアーを参照していた最新の pthread_barrier_init() 関数の結果として保持していた状態にリセットされます。
int pthread_barrier_wait(pthread_barrier_t *barrier);
#include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_wait(&barrier);
pthread_barrier_wait() 関数は、正常終了時に PTHREAD_BARRIER_SERIAL_THREAD を返します。この値は pthread.h で定義されており、このバリアーで同期がとられた 1 つの任意のスレッドに返されます。ほかの各スレッドには、0 が返されます。それ以外の場合は、エラーコードが返されます。
EINVAL
説明:barrier で指定された値が、初期化されたバリアーオブジェクトを表していません。
バリアーが必要なくなったら、そのバリアーを削除するようにすべきです。barrier が参照するバリアーを削除し、そのバリアーによって使用されているリソースをすべて解放するには、pthread_barrier_destroy(3C) を使用します。
int pthread_barrier_destroy(pthread_barrier_t *barrier);
#include <pthread.h> pthread_barrier_t barrier; int ret; ret = pthread_barrier_destroy(&barrier);
pthread_barrier_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:barrier の値が無効です。
EBUSY
説明:バリアーが別のスレッドによって使用されている間 (たとえば、pthread_barrier_wait() で使用されている間) にそのバリアーの削除の試行が検出されました。
pthread_barrierattr_init(3C) 関数は、バリアー属性オブジェクト attr を、実装によってこのオブジェクト用に定義された属性のデフォルト値に初期化します。現在、プロセス共有の属性のみが提供されており、この属性を取得および設定するために pthread_barrierattr_getpshared() および pthread_barrierattr_setpshared() 関数が使用されます。
バリアー属性オブジェクトを使用して 1 つ以上のバリアーを初期化したあと、この属性オブジェクトに影響するどの関数 (削除を含む) を使用しても、前に初期化されたバリアーには影響しません。
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
#include <pthread.h> pthread_barrierattr_t attr; int ret; ret = pthread_barrierattr_init(&attr);
pthread_barrierattr_init() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
ENOMEM
説明:バリアー属性オブジェクトを初期化するためのメモリーが足りません。
pthread_barrierattr_setpshared() 関数は、attr が参照する初期化された属性オブジェクト内のプロセス共有の属性を設定します。プロセス共有の属性は、次のいずれかの値を持ちます。
バリアーを操作できるのは、そのバリアーを初期化したスレッドと同じプロセス内で作成されたスレッドだけです。これはプロセス共有の属性のデフォルト値です。
バリアーが割り当てられているメモリーにアクセスできる任意のスレッドがバリアーを操作できます。
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
pthread_barrierattr_setpshared() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:attr の値が無効か、または pshared に指定された新しい値が無効です。
pthread_barrierattr_getpshared(3C) 関数は、attr が参照する属性オブジェクトからプロセス共有の属性の値を取得します。この値は、pthread_barrierattr_setpshared() 関数で設定されます。
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
pthread_barrierattr_getpshared() は、正常終了時に 0 を返し、attr のプロセス共有の属性の値を pshared パラメータが参照するオブジェクトに格納します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:attr の値が無効です。
pthread_barrierattr_destroy() 関数は、バリアー属性オブジェクトを削除します。削除された attr 属性オブジェクトは、pthread_barrierattr_init() を使用して再初期化することができます。
バリアー属性オブジェクトを使用して 1 つ以上のバリアーを初期化したあと、このオブジェクトを削除しても、前に初期化されたバリアーには影響しません。
#include <pthread.h> int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
pthread_barrierattr_destroy() は、正常終了時に 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。
EINVAL
説明:attr の値が無効です。
今までに説明した 4 種類の同期プリミティブは、プロセスの境界を越えて使用できます。プリミティブを設定するには、まず、その同期変数の領域が共有メモリーに確保されるようにし、次に該当する init() ルーチンを呼び出します。その前に、プロセス間に設定された共有属性でプリミティブを初期化しておく必要があります。
例 4–17 は、「生産者 / 消費者」問題の生産者と消費者をそれぞれ別のプロセスで表現したものです。メインルーチンは、子プロセスと共有されているメモリーを 0 で初期化し、それを自分のアドレス空間にマッピングします。
子プロセスが 1 つ生成され、消費者の処理が実行されます。親プロセスは生産者の処理を実行します。
この例では、生産者と消費者を呼び出す各駆動ルーチンも示しています。producer_driver() は stdin から文字を読み込み、producer() を呼び出します。consumer_driver() は consumer() を呼び出して文字を受け取り、stdout に書き出します。
例 4–17 のデータ構造は、条件変数による「生産者 / 消費者」の例のデータ構造 (例 4–4 を参照) と同じです。2 つのセマフォーは、いっぱいになったバッファー数と空のバッファー数をそれぞれ表します。これらのセマフォーは、バッファーが空になるまで生産者を待たせ、バッファーがいっぱいになるまで消費者を待たせます。
main() { int zfd; buffer_t *buffer; pthread_mutexattr_t mattr; pthread_condattr_t cvattr_less, cvattr_more; zfd = open("/dev/zero", O_RDWR); buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t), PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0); buffer->occupied = buffer->nextin = buffer->nextout = 0; pthread_mutex_attr_init(&mattr); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&buffer->lock, &mattr); pthread_condattr_init(&cvattr_less); pthread_condattr_setpshared(&cvattr_less, PTHREAD_PROCESS_SHARED); pthread_cond_init(&buffer->less, &cvattr_less); pthread_condattr_init(&cvattr_more); pthread_condattr_setpshared(&cvattr_more, PTHREAD_PROCESS_SHARED); pthread_cond_init(&buffer->more, &cvattr_more); if (fork() == 0) consumer_driver(buffer); else producer_driver(buffer); } void producer_driver(buffer_t *b) { int item; while (1) { item = getchar(); if (item == EOF) { producer(b, `\0'); break; } else producer(b, (char)item); } } void consumer_driver(buffer_t *b) { char item; while (1) { if ((item = consumer(b)) == '\0') break; putchar(item); } }
スレッドで使われる最も基本的な同期プリミティブは、相互排他ロックです。相互排他ロックは、メモリー使用量と実行時間の両面でもっとも効率的な機構です。相互排他ロックの主要目的は、リソースへのアクセスを直列化することです。
その次に効率の高いプリミティブは、条件変数です。条件変数の主要目的は、状態の変化に基づいてスレッドをブロックすることです。条件変数は、スレッド待ち機能を提供します。条件変数でスレッドをブロックする場合は、その前に相互排他ロックを獲得しなければなりません。また、pthread_cond_wait() から戻ったあとに相互排他ロックを解除しなければいけません。また、対応する pthread_cond_signal() 呼び出しまで状態の変更が行われる間、相互排他ロックを保持しておかなければなりません。
セマフォーは、条件変数より多くのメモリーを使用します。セマフォーは、状況によっては条件変数よりも簡単に使用できます。セマフォー変数は、制御でなく状態に基づいて機能するからです。また、ロックのように保持するという概念もありません。スレッドをブロックしているセマフォーに対して、どのスレッドもセマフォーの値を 1 増やすことができます。
読み取り/書き込みロックを使用すると、保護されたリソースに対する、並行する複数の読み取り操作や排他的な書き込み操作ができます。読み取り/書き込みロックは単一の実体で、読み取りモードまたは書き込みモードでロック可能です。リソースを変更するには、スレッドがまず排他書き込みロックを獲得する必要があります。すべての読み取りロックが開放されない限り、排他書き込みロックは許可されません。