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

第 4 章 同期オブジェクトを使ったプログラミング

この章では、スレッドで使用できる同期の手法と同期上の問題について説明します。

同期オブジェクトは、データと同じようにしてアクセスされるメモリー内の変数です。異なるプロセス内のスレッドは、通常はお互いに参照できませんが、スレッドが制御する共有メモリー内に格納されている同期オブジェクトを使用することにより、相互に同期をとることができます。

同期オブジェクトをファイルに置くこともできます。そうすれば、同期オブジェクトを作成したプロセスの消滅後も同期変数を有効にできます。

次の同期オブジェクトがあります。

以下のような状況で、同期は効果を発揮します。


注 –

32 ビットアーキテクチャでは、 long long は不可分 [原子操作は、それ以上小さい操作に分割できません。] ではなく、 2 つの 32 ビット値として読み書きされます。int 型、char 型、float 型、およびポインタは、SPARC 版マシンと x86 マシンでは不可分です。


相互排他ロック属性

相互排他ロック (mutex ロック) は、スレッドの実行を直列化したいときに使用します。相互排他ロックでスレッド間の同期をとるときは、通常はコードの危険領域が複数のスレッドによって同時に実行されないようにするという方法が用いられます。単一のスレッドのコードを保護する目的で相互排他ロックを使用することもできます。

デフォルトの mutex 属性を変更するには、属性オブジェクトを宣言して初期化します。多くの場合、アプリケーションの先頭部分の一箇所で設定しますので、mutex 属性は、すばやく見つけて簡単に変更できます。表 4–1 に、この節で説明する mutex 属性操作関数を示します。

表 4–1 mutex 属性ルーチン

操作 

参照先 

mutex 属性オブジェクトの初期化 

pthread_mutexattr_init(3THR)

mutex 属性オブジェクトの削除 

pthread_mutexattr_destroy(3THR)

mutex のスコープ設定 

pthread_mutexattr_setpshared(3THR)

mutex のスコープの値の取得 

pthread_mutexattr_getpshared(3THR)

mutex の型属性の設定 

pthread_mutexattr_settype(3THR)

mutex の型属性の取得 

pthread_mutexattr_gettype(3THR)

mutex 属性のプロトコルの設定 

pthread_mutexattr_setprotocol(3THR)

mutex 属性のプロトコルの取得 

pthread_mutexattr_getprotocol(3THR)

mutex 属性の優先順位上限の設定 

pthread_mutexattr_setprioceiling(3THR)

mutex 属性の優先順位上限の取得 

pthread_mutexattr_getprioceiling(3THR)

mutex の優先順位上限の設定 

pthread_mutex_setprioceiling(3THR)

mutex の優先順位上限の取得 

pthread_mutex_getprioceiling(3THR)

mutex の堅牢度属性の設定 

pthread_mutexattr_setrobust_np(3THR)

mutex の堅牢度属性の取得 

pthread_mutexattr_getrobust_np(3THR)

mutex のスコープ定義について、Solaris のスレッドと POSIX のスレッドとの相違点を表 4–2に示します。

表 4–2 mutex のスコープの比較

Solaris 

POSIX 

定義 

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

このプロセスと他のプロセスのスレッドの間で同期をとるために使用する。 

USYNC_PROCESS_ROBUST

POSIX に相当する定義なし 

異なるプロセスのスレッド間で安定的に同期をとるために使用する 

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

このプロセスのスレッドの間でだけ同期をとるために使用する。 

mutex 属性オブジェクトの初期化

pthread_mutexattr_init(3THR)

pthread_mutexattr_init(3THR) は、このオブジェクトに関連付けられた属性をデフォルト値に初期化します。各属性オブジェクトのための記憶領域は、実行時にスレッドシステムによって割り当てられます。

この関数が呼び出されたときの pshared 属性のデフォルト値は PTHREAD_PROCESS_PRIVATE で、初期化された mutex を 1 つのプロセスの中だけで使用できるという意味です。


プロトタイプ:
int	pthread_mutexattr_init(pthread_mutexattr_t *mattr);

#include <pthread.h>

pthread_mutexattr_t mattr;
int ret;

/* 属性をデフォルト値に初期化する */
ret = pthread_mutexattr_init(&mattr); 

mattr は不透明な型で、システムによって割り当てられた属性オブジェクトを含んでいます。mattr のスコープとして取り得る値は、PTHREAD_PROCESS_PRIVATE (デフォルト) と PTHREAD_PROCESS_SHARED です。

mutex 属性オブジェクトを再初期化するには、pthread_mutexattr_destroy(3THR) への呼び出しによって事前に削除しなければなりません。pthread_mutexattr_init() を呼び出すと、不透明なオブジェクトが割り当てられます。そのオブジェクトが削除されないと、結果的にメモリーリークを引き起こします。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、次の値を返します。


ENOMEM

メモリー不足のため、mutex 属性オブジェクトを初期化できません。

mutex 属性オブジェクトの削除

pthread_mutexattr_destroy(3THR)

pthread_mutexattr_destroy(3THR) は、pthread_mutexattr_init() によって生成された属性オブジェクトの管理に使用されていた記憶領域の割り当てを解除します。


プロトタイプ:
int	pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)

#include <pthread.h>

pthread_mutexattr_t mattr;
int ret;

/* 属性を削除する */
ret = pthread_mutexattr_destroy(&mattr);

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

mattr で指定された値が無効です。

mutex のスコープ設定

pthread_mutexattr_setpshared(3THR)

pthread_mutexattr_setpshared(3THR) は、mutex 変数のスコープを設定します。

mutex 変数の値は、プロセス専用 (プロセス内) とシステム共通 (プロセス間) のどちらかです。pshared 属性を PTHREAD_PROCESS_SHARED 状態に設定して mutex を生成し、その mutex が共有メモリー内に存在する場合、その mutex は複数のプロセスのスレッドの間で共有できます。これは、オリジナルの Solaris スレッドにおいて mutex_init()USYNC_PROCESS フラグを使用するのに相当します。


プロトタイプ:
int	pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr,
    int pshared);

#include <pthread.h>

pthread_mutexattr_t mattr;int ret;ret = pthread_mutexattr_init(&mattr);
/*
 * デフォルト値にリセットする: private
 */
ret = pthread_mutexattr_setpshared(&mattr,
     PTHREAD_PROCESS_PRIVATE);

mutex の pshared 属性を PTHREAD_PROCESS_PRIVATE に設定した場合、その mutex を操作できるのは同じプロセスで生成されたスレッドだけです。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

mattr で指定された値が無効です。

mutex のスコープの値の取得

pthread_mutexattr_getpshared(3THR)

pthread_mutexattr_getpshared(3THR) は、pthread_mutexattr_setpshared() によって定義された、mutex 変数のスコープを返します。


プロトタイプ:
int	pthread_mutexattr_getpshared(pthread_mutexattr_t *mattr,
    int *pshared);

#include <pthread.h>

pthread_mutexattr_t mattr;int pshared, ret;

/* mutex の pshared を取得する */
ret = pthread_mutexattr_getpshared(&mattr, &pshared);

属性オブジェクト mattrpshared の現在値を取得します。これは PTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE のどちらかです。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

mattr で指定された値が無効です。

mutex の型属性の設定

pthread_mutexattr_settype(3THR)


#include <pthread.h>

int pthread_mutexattr_settype(pthread_mutexattr_t  *attr , int type);

pthread_mutexattr_settype(3THR) は、 mutex の 型 (type) 属性を設定します。型 属性のデフォルト値は PTHREAD_MUTEX_DEFAULT です。

型 (type) 引数は mutex の型を指定します。有効な mutex 型を以下に示します。


PTHREAD_MUTEX_NORMAL

この型の 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 を繰り返しロックしようとした場合、引き起こされる動作は未定義です。この型の mutex を、ロックしていないスレッドがロック解除しようとした場合、引き起こされる動作は未定義です。この型の、ロックされていない mutex をロック解除しようとした場合、引き起こされる動作は未定義です。この型の mutex は、他の mutex 型に割り当てることができます。Solaris スレッドでは、PTHREAD_PROCESS_DEFAULTPTHREAD_PROCESS_NORMAL に割り当てられます。

戻り値

pthread_mutexattr_settype 関数は、正常に終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

type の値が無効です。


EINVAL

attr が示す値は無効です。

mutex の型属性の取得

pthread_mutexattr_gettype(3THR)


#include <pthread.h>

int pthread_mutexattr_gettype(pthread_mutexattr_t  *attr , int  *type);

pthread_mutexattr_gettype(3THR) は、pthread_mutexattr_settype() によって設定された、 mutex の 型 ( type) 属性を取得します。型属性のデフォルト値は PTHREAD_MUTEX_DEFAULT です。

型 (type) 引数は mutex の型を指定します。有効な mutex 型を以下に示します。

各型の説明については、pthread_mutexattr_settype(3THR)を参照してください。

mutex 属性のプロトコルの設定

pthread_mutexattr_setprotocol(3THR)

pthread_mutexattr_setprotocol(3THR) は、mutex 属性オブジェクトのプロトコル属性を設定します。


#include <pthread.h>

int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol);

attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。

protocol には、mutex 属性オブジェクトに適用されるプロトコルを指定します。

pthread.h に定義可能な protocol の値は、PTHREAD_PRIO_NONEPTHREAD_PRIO_INHERIT、または PTHREAD_PRIO_PROTECT です。

スレッドが PTHREAD_PRIO_INHERIT または PTHREAD_PRIO_PROTECT で初期化された mutex を所有しており、sched_setparam() の呼び出しなどによってそのスレッドの元の優先順位が変更されている場合は、スケジューラは新しい優先順位のスケジューリングキューの末尾にそのスレッドを移動しません。同様に、PTHREAD_PRIO_INHERIT または PTHREAD_PRIO_PROTECT で初期化された mutex をスレッドがロック解除して、そのスレッドの元の優先順位が変更されている場合は、スケジューラは新しい優先順位のスケジューリングキューの末尾にそのスレッドを移動しません。

PTHREAD_PRIO_INHERIT で初期化された mutex と PTHREAD_PRIO_PROTECT で初期化された mutex を複数同時に所有しているスレッドは、これらのプロトコルのいずれかで獲得された最高の優先順位で実行します。

戻り値

pthread_mutexattr_setprotocol() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次のどちらかの条件が検出されると、pthread_mutexattr_setprotocol() は失敗し、対応する値を返します。


ENOSYS

_POSIX_THREAD_PRIO_INHERIT_POSIX_THREAD_PRIO_PROTECT のどちらのオプションも定義されておらず、この実装はこの関数をサポートしていません。


ENOTSUP

protocol で指定された値はサポートされていない値です。

次のどちらかの条件が検出されると、pthread_mutexattr_setprotocol() は失敗し、対応する値を返します。


EINVAL

attr または protocol に指定した値は無効です。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex 属性のプロトコルの取得

pthread_mutexattr_getprotocol(3THR)

pthread_mutexattr_getprotocol(3THR) は、mutex 属性オブジェクトのプロトコル属性を取得します。


#include <pthread.h>

int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, 
                                                        int *protocol);

attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。

protocol には、プロトコル属性が入ります。PTHREAD_PRIO_NONEPTHREAD_PRIO_INHERIT、または PTHREAD_PRIO_PROTECT です。

戻り値

pthread_mutexattr_getprotocol() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件が検出されると、pthread_mutexattr_getprotocol() は失敗し、対応する値を返します。


ENOSYS

_POSIX_THREAD_PRIO_INHERIT_POSIX_THREAD_PRIO_PROTECT のどちらのオプションも定義されておらず、この実装はこの関数をサポートしていません。

次のどちらかの条件が検出されると、pthread_mutexattr_getprotocol() は失敗し、条件に対応する値を返します。


EINVAL

attr で指定された値が無効です。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex 属性の優先順位上限の設定

pthread_mutexattr_setprioceiling(3THR)

pthread_mutexattr_setprioceiling(3THR) は、mutex 属性オブジェクトの優先順位上限属性を設定します。


#include <pthread.h>

int pthread_mutexattr_setprioceiling(pthread_mutexatt_t *attr, 
                                                      int prioceiling, 
                                                      int *oldceiling);

attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。

prioceiling には、初期化された mutex の優先順位上限を指定します。この上限は、mutex によって保護されている重要領域が実行される最小の優先レベルを定義します。prioceiling は、SCHED_FIFO によって定義される優先順位の最大範囲内にあります。優先順位が逆転しないように、特定の mutex をロックするすべてのスレッドの中で最も高い優先順位と同じかまたはそれを上回る優先順位を prioceiling として設定します。

oldceiling には古い優先順位上限の値が入ります。

戻り値

pthread_mutexattr_setprioceiling() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次のいずれかの条件が検出されると、pthread_mutexattr_setprioceiling() は失敗し、対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO_PROTECT が定義されておらず、この実装はこの関数をサポートしていません。

次のどちらかの条件が検出されると、pthread_mutexattr_setprioceiling() は失敗し、対応する値を返します。


EINVAL

attr または prioceiling に指定した値は無効です。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex 属性の優先順位上限の取得

pthread_mutexattr_getprioceiling(3THR)

pthread_mutexattr_setprioceiling(3THR) は、mutex 属性オブジェクトの優先順位上限属性を取得します。


#include <pthread.h>

int pthread_mutexattr_getprioceiling(const pthread_mutexatt_t *attr, 
                                                          int *prioceiling);

attr は、先の pthread_mutexattr_init() の呼び出しによって作成された属性オブジェクトを指します。


注 –

attr mutex 属性オブジェクトに優先順位上限属性が含まれるのは、シンボル _POSIX_THREAD_PRIO_PROTECT が定義されている場合だけです。


pthread_mutexattr_getprioceiling() は、初期化された mutex の優先順位上限、mutexprioceiling で返します。この上限は、mutex によって保護されている重要領域が実行される最小の優先レベルを定義します。prioceiling は、SCHED_FIFO によって定義される優先順位の最大範囲内にあります。優先順位が逆転しないように、特定の mutex をロックするすべてのスレッドの中で最も高い優先順位と同じかまたはそれを上回る優先順位を prioceiling として設定します。

戻り値

pthread_mutexattr_getprioceiling() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件が検出されると、pthread_mutexattr_getprioceiling() は失敗し、対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO_PROTECT が定義されておらず、この実装はこの関数をサポートしていません。

次のどちらかの条件が検出されると、pthread_mutexattr_getprioceiling() は失敗し、対応する値を返します。


EINVAL

attr で指定された値が無効です。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex の優先順位上限の設定

pthread_mutex_setprioceiling(3THR)

pthread_mutex_setprioceiling(3THR) は、mutex の優先順位上限を設定します。


#include <pthread.h>

int pthread_mutex_setprioceiling(pthread_mutex_t *mutex, 
                                               int prioceiling, 
                                               int *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_mutexatt_setprioceiling() は失敗し、それに対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO_PROTECT が定義されておらず、この実装はこの関数をサポートしていません。

次のいずれかの条件が検出されると、pthread_mutex_setprioceiling() は失敗し、対応する値を返します。


EINVAL

prioceiling で要求された優先順位が範囲外です。


EINVAL

mutex で指定された値は現在の既存の mutex を参照していません。


ENOSYS

この実装は mutex の優先順位上限プロトコルをサポートしていません。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex の優先順位上限の取得

pthread_mutex_getprioceiling(3THR)

pthread_mutex_getprioceiling(3THR) は、mutex の優先順位上限を取得します。


#include <pthread.h>

int pthread_mutex_getprioceiling(const pthread_mutex_t *mutex, 
                                                   int *prioceiling);

pthread_mutex_getprioceiling() は、mutex の優先順位上限、つまり prioceiling を返します。

戻り値

pthread_mutex_getprioceiling() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件が検出されると、pthread_mutexatt_getprioceiling() は失敗し、対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO_PROTECT が定義されておらず、この実装はこの関数をサポートしていません。

次のいずれかの条件が検出されると、pthread_mutex_getprioceiling() は失敗し、対応する値を返します。


EINVAL

mutex で指定された値は現在の既存の mutex を参照していません。


ENOSYS

この実装は mutex の優先順位上限プロトコルをサポートしていません。


EPERM

呼び出し元はこの操作を行うための権限を持っていません。

mutex の堅牢度属性の設定

pthread_mutexattr_setrobust_np(3THR)

pthread_mutexattr_setrobust_np(3THR) は、mutex 属性オブジェクトの堅牢度属性を設定します。


#include <pthread.h>

int pthread_mutexattr_setrobust_np(pthread_mutexattr_t *attr, 
                                                  int *robustness);

注 –

pthread_mutexattr_setrobust_np() が適用されるのは、シンボル _POSIX_THREAD_PRIO_INHERIT が定義されている場合だけです。


attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。

robustness は、mutex の所有者が終了した場合の動作を定義します。pthread.h に定義可能な robustness の値は、PTHREAD_MUTEX_ROBUST_NP または PTHREAD_MUTEX_STALLED_NP です。デフォルト値は、PTHREAD_MUTEX_STALLED_NP です。

戻り値

pthread_mutexattr_setrobust_np() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件のいずれかが検出されると、pthread_mutexattr_setrobust_np() は失敗し、対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO__INHERIT が定義されていないか、あるいはこの実装が pthread_mutexattr_setrobust_np() 関数をサポートしていません。


ENOTSUP

robustness で指定された値はサポートされていません。

次の条件が検出されると、pthread_mutexattr_setrobust_np() は失敗します。


EINVAL

attr または robustness で指定された値は無効です。

mutex の堅牢度属性の取得

pthread_mutexattr_getrobust_np(3THR)

pthread_mutexattr_getrobust_np(3THR) は、mutex 属性オブジェクトの堅牢度属性を取得します。


#include <pthread.h>

int pthread_mutexattr_getrobust_np(const pthread_mutexattr_t *attr, 
                                                        int *robustness);

注 –

pthread_mutexattr_getrobust_np() が適用されるのは、シンボル_POSIX_THREAD_PRIO_INHERIT が定義されている場合だけです。


attr は、先の pthread_mutexattr_init() の呼び出しによって作成された mutex 属性オブジェクトを指します。

robustness は、mutex 属性オブジェクトの堅牢度属性の値です。

戻り値

pthread_mutexattr_getrobust_np() は、正常終了すると 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

次の条件のいずれかが検出されると、pthread_mutexattr_getrobust_np() は失敗し、対応する値を返します。


ENOSYS

オプション _POSIX_THREAD_PRIO__INHERIT が定義されていないか、あるいはこの実装が pthread_mutexattr_getrobust_np() 関数をサポートしていません。

次の条件が検出されると、pthread_mutexattr_getrobust_np() は失敗します。


EINVAL

attr または robustness で指定された値は無効です。

相互排他ロックの使用方法

表 4–3 に、この章で説明する mutex ロック操作関数を示します。

表 4–3 相互排他ロック操作ルーチン

操作 

参照先 

mutex の初期化 

pthread_mutex_init(3THR)

mutex の整合性保持 

pthread_mutex_consistent_np(3THR)

mutex のロック 

pthread_mutex_lock(3THR)

mutex のロック解除 

pthread_mutex_unlock(3THR)

ブロックしないで行う mutex のロック 

pthread_mutex_trylock(3THR)

mutex の削除 

pthread_mutex_destroy(3THR)

デフォルトスケジューリング方針 SCHED_OTHER は、スレッドによるロックの獲得順序を指定していません。複数のスレッドが mutex を待っているときの獲得の順序は不定です。競合するときは、スレッドを優先順位でブロック解除するというのがデフォルト動作です。

mutex の初期化

pthread_mutex_init(3THR)

pthread_mutex_init(3THR) は、mp が指す mutex をデフォルト値に初期化 (mattr が NULL の場合) するか、pthread_mutexattr_init() ですでに設定されている mutex 属性を指定するときに使用します(Solaris スレッドについては、mutex_init(3THR)を参照)。


プロトタイプ:
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 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EBUSY

mp で示されたオブジェクト (初期化されているが、まだ削除されていない mutex) の再初期化の試行が検出されました。


EINVAL

mattr 属性値が無効です。その mutex は変更されていません。


EFAULT

mp が指す mutex のアドレスが無効です。

mutex の整合性保持

pthread_mutex_consistent_np(3THR)


#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() は失敗します。


ENOSYS

オプション _POSIX_THREAD_PRIO_INHERIT が定義されていないか、あるいはこの実装が pthread_mutex_consistent_np() 関数をサポートしていません。

次の条件が検出されると、pthread_mutex_consistent_np() は失敗します。


EINVAL

mutex で指定された値は無効です。

mutex のロック

pthread_mutex_lock(3THR)


プロトタイプ:
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(3THR) は、mutex が指す mutex をロックします。pthread_mutex_lock() から制御が戻ると、mutex がロックされ、呼び出しスレッドが所有者になります。mutex が別のスレッドによってすでにロックされている (所有されている) 場合は、呼び出しスレッドは mutex が使用可能になるまでブロックされます (Solaris スレッドについては、cond_signal(3THR)を参照)。

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 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EAGAIN

mutex の再帰的なロックが最大数を超えるため、mutex を獲得できません。


EDEADLK

現在のスレッドがすでにその mutex を獲得しています。

シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されており、pthread_mutexattr_setrobust_np()robustness 引数が PTHREAD_MUTEX_ROBUST_NP である場合、この関数は失敗し、次の値を返します。


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 を持つロックを獲得した所有者が終了すると、次の所有者が EOWNERDEAD を持つロックを獲得します。


ENOTRECOVERABLE

獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。


ENOMEM

同時に保持される mutex の上限数を超えています。

mutex のロック解除

pthread_mutex_unlock(3THR)

pthread_mutex_unlock(3THR) は、mutex が指す mutex のロックを解除します (Solaris スレッドについては、mutex_unlock(3THR)を参照)。


プロトタイプ:
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 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EPERM

現在のスレッドは mutex を所有していません。

ブロックしないで行う mutex のロック

pthread_mutex_trylock(3THR)

pthread_mutex_trylock(3THR) は、mutex が指す mutex のロックを試みます (Solaris スレッドについては、mutex_trylock(3THR)を参照)。


プロトタイプ:
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 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EBUSY

mutex が指している mutex はすでにロックされているため、獲得できません。


EAGAIN

mutex に繰り返し行われたロック回数が最大数を超えるため、mutex を所有できません。

シンボル _POSIX_THREAD_PRIO_INHERIT が定義されていて、mutex がプロトコル属性値 PTHREAD_PRIO_INHERIT で初期化されており、pthread_mutexattr_setrobust_np()robustness 引数が PTHREAD_MUTEX_ROBUST_NP である場合、この関数は失敗し、次の値を返します。


EOWNERDEAD

この 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 を持つロックを獲得します。


ENOTRECOVERABLE

獲得しようとしている mutex は、ロックの保持中に終了した前の所有者によって回復不能にされた状態を保護しています。mutex は獲得されませんでした。ロックが以前に EOWNERDEAD を指定されて獲得され、所有者が状態をクリーンアップできず、mutex の状態を整合させないで mutex をロック解除した場合に、この状況が発生します。


ENOMEM

同時に保持される mutex の上限数を超えています。

mutex の削除

pthread_mutex_destroy(3THR)

pthread_mutex_destroy(3THR) は、mp が指す mutex に関連するすべての状態を削除します (Solaris スレッドについては、mutex_destroy(3THR)を参照)。


プロトタイプ:
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 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


EINVAL

mp で指定された値が、初期化された mutex オブジェクトを表していません。

mutex ロックのコード例

例 4–1 に、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 つの関数は、相互排他 (mutex) ロックをそれぞれ別の目的で使用しています。increment_count() 関数は、相互排他ロックによって共有変数の不可分操作による更新を保証しています。get_count() 関数は、相互排他ロックによって 64 ビット値の count が不可分に読み取られるようにしています。32 ビットアーキテクチャでは、long long は実際には 2 つの 32 ビット値として処理されます。

整数はほとんどのマシンで共通のワードサイズであるため、整数値の読み取りは不可分操作です。

ロック序列の使用

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


例 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 はロックできないという規定を守るようにします。

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


例 4–3 条件付きロック

スレッド 1 

スレッド 2 

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

/* 解放 */ 

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);  

for (; ;)

{ pthread_mutex_lock(&m2);

if (pthread_mutex_trylock(&m1)==0)

/* 獲得成功 */ */  

break;

/* 獲得失敗 */ 

pthread_mutex_unlock(&m2);

}

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

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


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

これを防ぐため、スレッド 2 は pthread_mutex_trylock() を呼び出し、mutex がロックされていなければロックします。ロックされていれば、スレッド 2 はただちにエラーを返します。その時点で、スレッド 2 は mutex の 2 を解放しなければなりません。その結果、スレッド 1 は mutex の 2 をロックでき、最終的には mutex の 1 と 2 の両方を解放します。

片方向リンクリストの入れ子のロック

例 4–4例 4–5で、一度に 3 つのロックを獲得する場合を説明します。この例では、デッドロックを防ぐために規定された順序でロックします。


例 4–4 片方向リンクのリスト構造体


typedef struct node1 {
    int value;
    struct node1 *link;
    pthread_mutex_t lock;
} node1_t;

node1_t ListHead;

この例で使用する片方向リンクのリスト構造体は、各ノードに相互排他ロックを含んでいます。このリストから特定のノードを削除する場合は、最初に ListHead (これが削除されることはない) の位置からリストをたどって目的のノードを探します。

この検索を同時並行的に行われる削除から保護するために、各ノードをロックしてからノードの内容にアクセスしなければなりません。すべての検索が ListHead の位置から開始されるので、常にリストの順序でロックされます。このため、デッドロックは決して発生しません。

目的のノードが見つかった時は、この変更がそのノードと直前のノードの両方に影響を与えるため、両方をロックします。直前のノードのロックが常に最初に獲得されるので、ここでもデッドロックの心配はありません。例 4–5 は、片方向リンクリストから特定のノードを削除する C コードを示しています。


例 4–5 片方向リンクリストの入れ子のロック


node1_t *delete(int value)
{
    node1_t *prev, *current;

    prev = &ListHead;
    pthread_mutex_lock(&prev->lock);
    while ((current = prev->link) != NULL) {
        pthread_mutex_lock(&current->lock);
        if (current->value == value) {
            prev->link = current->link;
            pthread_mutex_unlock(&current->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 つのノードに関連付けられると、そのノードと次のノードに対して操作を行います。この状況ではロック序列は適用できません。明らかに階層 (つまり、リンクをたどる順番) が循環的だからです。


例 4–6 循環リンクリスト


typedef struct node2 {
    int value;
    struct node2 *link;
    pthread_mutex_t lock;
} node2_t;

例 4–7 では 2 つのノードをロックし、両方のノードに対してある操作を行なっている C コードを示します。


例 4–7 循環リンクリストの入れ子のロック


void Hit Neighbor(node2_t *me) {
    while (1) {
        pthread_mutex_lock(&me->lock);
        if (pthread_mutex_lock(&me->link->lock)!= 0) {
            /* ロック失敗 */             
            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);
}
 


条件変数の属性

条件変数は、ある条件が真になるまでスレッドを不可分にブロックしたいときに使用します。必ず相互排他ロックとともに使用します。

条件変数を使うと、特定の条件が真になるまでスレッドを不可分にブロックできます。この条件判定は、相互排他ロックにより保護された状態で行います。

条件が偽のとき、スレッドは通常は条件変数でブロック状態に入り、相互排他ロックを原子的操作により解除して、条件が変更されるのを待ちます。別のスレッドが条件を変更すると、そのスレッドはそれに関連する条件変数にシグナルを送り、その条件変数でブロックしているスレッドを呼び起こします。呼び起こされたスレッドは再度相互排他ロックを獲得し、条件を再び評価します。

異なるプロセスに所属するスレッドの間で、条件変数を使って同期をとるためには、連携するそれらのプロセスの間で共有される書き込み可能なメモリーに、条件変数の領域を確保する必要があります。

スケジューリング方針は、ブロックされたスレッドがどのように呼び起こされるかを決定します。デフォルト SCHED_OTHER の場合、スレッドは優先順位に従って呼び起こされます。

条件変数の属性は、使用する前に設定して初期化しておかなければなりません。条件変数の属性を操作する関数を表 4–4に示します。

表 4–4 条件変数の属性

操作 

参照先 

条件変数の属性の初期化 

pthread_condattr_init(3THR)

条件変数の属性の削除 

pthread_condattr_destroy(3THR)

条件変数のスコープの設定  

pthread_condattr_setpshared(3THR)

条件変数のスコープの取得 

pthread_condattr_getpshared(3THR)

条件変数のスコープ定義について、Solaris スレッドと POSIX スレッドの相違点を表 4–5に示します。

表 4–5 条件変数のスコープの比較

Solaris 

POSIX 

定義 

USYNC_PROCESS

PTHREAD_PROCESS_SHARED

このプロセスと他のプロセスのスレッドの間で同期をとるために使用する。 

USYNC_THREAD

PTHREAD_PROCESS_PRIVATE

このプロセスのスレッドの間でだけ同期をとるために使用する。 

条件変数の属性の初期化

pthread_condattr_init(3THR)

pthread_condattr_init(3THR) は、このオブジェクトに関連付けられた属性をデフォルト値に初期化します。各属性オブジェクトのための記憶領域は、実行時にスレッドシステムによって割り当てられます。この関数が呼び出されたときの pshared 属性のデフォルト値は PTHREAD_PROCESS_PRIVATE で、初期化された条件変数を 1 つのプロセスの中だけで使用できるという意味です。


プロトタイプ:
int	pthread_condattr_init(pthread_condattr_t *cattr);

#include <pthread.h>
pthread_condattr_t  cattr;
int ret;

/* 属性をデフォルト値に初期化 */
ret = pthread_condattr_init(&cattr); 

cattr は不透明なデータ型で、システムによって割り当てられた属性オブジェクトを格納します。cattr のスコープとして取りうる値は、PTHREAD_PROCESS_PRIVATE (デフォルト) と PTHREAD_PROCESS_SHARED です。

条件変数属性を再使用するには、pthread_condattr_destroy(3THR) によって事前に削除しなければなりません。pthread_condattr_init() 呼び出しは、不透明なオブジェクトへのポインタを戻します。そのオブジェクトが削除されないと、結果的にメモリーリークを引き起こします。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


ENOMEM

メモリーが足りなくて、スレッド属性オブジェクトを初期化できません。


EINVAL

cattr で指定された値が無効です。

条件変数の属性の削除

pthread_condattr_destroy(3THR)

pthread_condattr_destroy(3THR) は記憶領域を解除し、属性オブジェクトを無効にします。


プロトタイプ:
int	pthread_condattr_destroy(pthread_condattr_t *cattr);

#include <pthread.h>
pthread_condattr_t cattr;
int ret;

/* 属性の削除 */
ret
 = pthread_condattr_destroy(&cattr); 

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

cattr で指定された値が無効です。

条件変数のスコープの設定

pthread_condattr_setpshared(3THR)

pthread_condattr_setpshared(3THR) は、プロセス専用 (プロセス内) とシステム共通 (プロセス間) のどちらかに条件変数のスコープを設定します。pshared 属性を PTHREAD_PROCESS_SHARED 状態に設定して条件変数を生成し、その条件変数が共有メモリー内に存在する場合、その条件変数は複数のプロセスのスレッドの間で共有できます。これは、オリジナルの Solaris スレッドにおいて mutex_init()USYNC_PROCESS フラグを使用するのに相当します。

mutex の pshared 属性を PTHREAD_PROCESS_PRIVATE (デフォルト値) に設定した場合、その mutex を操作できるのは同じプロセスで生成されたスレッドに限られます。PTHREAD_PROCESS_PRIVATE を使用した場合、その動作はオリジナルの Solaris スレッドにおいて cond_init() 呼び出しで USYNC_THREAD フラグを使用したとき、すなわち局所条件変数と同じになります。PTHREAD_PROCESS_SHARED は広域条件変数に相当します。


プロトタイプ:
int	pthread_condattr_setpshared(pthread_condattr_t *cattr,
    int pshared);

#include <pthread.h>

pthread_condattr_t cattr;
int ret;

/* 全プロセス */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);

/* 1 つのプロセス内 */
ret = pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_PRIVATE);

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

cattr または pshared の値が無効です。

条件変数のスコープの取得

pthread_condattr_getpshared(3THR)

pthread_condattr_getpshared(3THR) は属性オブジェクト cattrpshared の現在のスコープ値を取得します。これは PTHREAD_PROCESS_SHAREDPTHREAD_PROCESS_PRIVATE のどちらかです。


プロトタイプ:
int	pthread_condattr_getpshared(const pthread_condattr_t *cattr,
    int *pshared);

#include <pthread.h>

pthread_condattr_t cattr;
int pshared;
int ret;

/* 条件変数の pshared 値を取得する */
ret = pthread_condattr_getpshared(&cattr, &pshared);

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

cattr の値が無効です。

条件変数の使用方法

この節では条件変数の使用方法を説明します。表 4–6 にそのための関数を示します。

表 4–6 条件変数関数

操作 

参照先 

条件変数の初期化 

pthread_cond_init(3THR)

条件変数によるブロック 

pthread_cond_wait(3THR)

特定のスレッドのブロック 

pthread_cond_signal(3THR)

時刻指定のブロック 

pthread_cond_timedwait(3THR)

間隔指定のブロック 

pthread_cond_reltimedwait_np(3THR)

全スレッドのブロック解除 

pthread_cond_broadcast(3THR)

条件変数の削除 

pthread_cond_destroy(3THR)

条件変数の初期化

pthread_cond_init(3THR)

pthread_cond_init(3THR) は、cv が指す条件変数をデフォルト値 (cattr が NULL) に初期化します。また、pthread_condattr_init() ですでに設定してある条件変数の属性を指定することもできます。cattr を NULL にするのは、デフォルト条件変数属性オブジェクトのアドレスを渡すのと同じですが、メモリーのオーバーヘッドがありません。(Solaris スレッドについては、cond_init(3THR)を参照)。


プロトタイプ:
int	pthread_cond_init(pthread_cond_t *cv,
    const pthread_condattr_t *cattr);

#include <pthread.h>

pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;

/* 条件変数をデフォルト値に初期化 */
ret = pthread_cond_init(&cv, NULL);

/* 条件変数の初期化 */
ret = pthread_cond_init(&cv, &cattr); 

静的に定義された条件変数は、マクロ PTHREAD_COND_INITIALIZER で、デフォルト属性をもつように直接初期化できます。この効果は、NULL 属性を指定して pthread_cond_init() を動的に割り当てるのと同じです。エラーチェックは行われません。

複数のスレッドで同じ条件変数を同時に初期化または再初期化しないでください。条件変数を再初期化または削除する場合、アプリケーションでその条件変数が現在使用されていないことを確認しなければなりません。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

cattr で指定された値が無効です。


EBUSY

その条件変数は現在使用されています。


EAGAIN

必要なリソースが利用できません。


ENOMEM

メモリー不足のため条件変数を初期化できません。

条件変数によるブロック

pthread_cond_wait(3THR)

pthread_cond_wait(3THR) は、mp が指す相互排他ロックを不可分操作により解放し、cv が指す条件変数で呼び出しスレッドをブロックします (Solaris スレッドについては、cond_wait(3THR)を参照)。


プロトタイプ:
int	pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);

#include <pthread.h>

pthread_cond_t cv;
pthread_mutex_t mp;
int ret;

/* 条件変数でブロック */
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 ロックを獲得しようとします。

呼び起こされたスレッドが mutex を再度獲得して pthread_cond_wait()から戻る前に条件が変わり、また待機しているスレッドが誤って呼び起こされたりすることがあるので、待機の条件を再度テストしてから、 pthread_cond_wait() の場所から実行を再開してください。条件チェックを while() ループに入れ、そこで pthread_cond_wait() を呼び出すようにすることをお勧めします。


    pthread_mutex_lock();
        while(condition_is_false)
            pthread_cond_wait();
    pthread_mutex_unlock();

条件変数で複数のスレッドがブロックされているとき、それらのスレッドが、どの順番でブロックが解除されるかは不定です。


注 –

pthread_cond_wait() は取り消しポイントです。保留状態になっている取り消しがあって、呼び出しスレッドが取り消しを有効 (使用可能) にしている場合、そのスレッドは終了し、ロックしている間にクリーンアップハンドラの実行を開始します。


戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

cv または mp で指定された値が無効です。

1 つのスレッドのブロック解除

pthread_cond_signal(3THR)

pthread_cond_signal(3THR) は、cv が指す条件変数でブロックされている 1 つのスレッドのブロックを解除します (Solaris スレッドについては、cond_signal(3THR)を参照)。


プロトタイプ:
int	pthread_cond_signal(pthread_cond_t *cv);

#include <pthread.h>

pthread_cond_t cv;
int ret;

/* 条件変数がシグナルを送る */
ret = pthread_cond_signal(&cv); 

pthread_cond_signal() は、シグナルを送ろうとしている条件変数で使用されたものと同じ mutex ロックを獲得した状態で呼び出してください。そうしないと、関連する条件変数が評価されてから pthread_cond_wait() でブロック状態に入るまでの間に条件変数にシグナルが送られる可能性があり、その場合 pthread_cond_wait() は永久に待ち続けることになります。

スケジューリング方針は、ブロックされたスレッドがどのように呼び起こされるかを決定します。SCHED_OTHER の場合、スレッドは優先順位に従って呼び起こされます。

スレッドがブロックされていない条件変数に対して pthread_cond_signal() を実行しても無視されます。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

cv が指すアドレスが正しくありません。

例 4–8 に、pthread_cond_wait ()pthread_cond_signal() の使用方法を示します。


例 4–8 pthread_cond_wait()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_timedwait(3THR)


プロトタイプ:
int	pthread_cond_timedwait(pthread_cond_t *cv,
    pthread_mutex_t *mp, const struct timespec *abstime);

#include <pthread.h>
#include <time.h>

pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;

/* 条件変数で指定した時刻までブロック */
ret = pthread_cond_timedwait(&cv, &mp, &abstime); 

pthread_cond_timedwait(3THR) は、abstime で指定した時刻を過ぎるとブロック状態を解除する点を除いて、pthread_cond_wait() と同じ動作をします。pthread_cond_timedwait() が戻るときは、たとえエラーを戻したときでも、常に mutex は呼び出しスレッドがロックして保持している状態です (Solaris スレッドについては、cond_timedwait(3THR)を参照)。

pthread_cond_timedwait() のブロック状態が解除されるのは、条件変数にシグナルが送られてきたときか、一番最後の引数で指定した時刻を過ぎたときです。


注 –

pthread_cond_timedwait() は、取り消しポイントでもあります。


戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


EINVAL

cv または abstime が不当なアドレスを指しています。


ETIMEDOUT

abstime で指定された時刻を過ぎています。

時間切れの指定は時刻で行うため、時間切れ時刻を再計算する必要がなく、効率的に条件を再評価できます (詳細は、例 4–9を参照してください)。


例 4–9 時刻指定のブロック


pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
while (cond == FALSE) {
    err = pthread_cond_timedwait(&c, &m, &to);
    if (err == ETIMEDOUT) {
        /* 時間切れの場合の処理 */
        break;
    }
}
pthread_mutex_unlock(&m);

間隔指定のブロック

pthread_cond_reltimedwait_np(3THR)


プロトタイプ:
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;

/* 条件変数でブロック */
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime); 

pthread_cond_reltimedwait_np(3THR) の使用方法は、pthread_cond_timedwait() の場合と同じです。ただし、pthread_cond_reltimedwait_np() の最後の引数には、未来の絶対日時ではなく、相対時間間隔を指定します。pthread_cond_reltimedwait_np() は、たとえエラーを戻したときでも、常に mutex は呼び出しスレッドがロックして保持している状態で戻ります (Solaris スレッドについては cond_reltimedwait(3THR)を参照)。 pthread_cond_reltimedwait_np () 関数は、条件のシグナルを受け取るか、最後の引数に指定されている時間間隔が経過するまで、ブロックします。


注 –

pthread_cond_reltimedwait_np() は、取り消しポイントでもあります。


戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


EINVAL

cv または reltime が不当なアドレスを指しています。


ETIMEDOUT

reltime に指定されている時間間隔が経過しました。

全スレッドのブロック解除

pthread_cond_broadcast(3THR)


プロトタイプ:
int	pthread_cond_broadcast(pthread_cond_t *cv);

#include <pthread.h>

pthread_cond_t cv;
int ret;

/* 条件変数すべてがシグナルを受ける */
ret = pthread_cond_broadcast(&cv); 

pthread_cond_broadcast(3THR) は、cv (pthread_cond_wait() で指定された) が指す条件変数でブロックされている、すべてのスレッドのブロックを解除します。スレッドがブロックされていない条件変数に対して pthread_cond_broadcast() を実行しても無視されます。(Solaris スレッドについては、cond_broadcast(3THR)を参照)。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

cv が指すアドレスが正しくありません。

条件変数に対するブロードキャストの例

pthread_cond_broadcast() は、条件変数でブロックされていたすべてのスレッドにもう一度相互排他ロックを争奪させるので、慎重に使用してください。たとえば、pthread_cond_broadcast() を使用して、可変量のリソースをそのリソースが解放されるときにスレッド間で争奪させることができます (例 4–10 を参照してください)。


例 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() の呼び出しはどちらを先に行なってもかまいません。

pthread_cond_broadcast() は、シグナルを送ろうとしている条件変数で使用されたものと同じ相互排他ロックを獲得した状態で呼び出してください。そうしないと、関連する条件変数が評価されてから pthread_cond_wait() でブロック状態に入るまでの間に条件変数にシグナルが送られる可能性があり、その場合 pthread_cond_wait() は永久に待ち続けることになります。

条件変数の削除

pthread_cond_destroy(3THR)

pthread_cond_destroy(3THR) は、cv が指す条件変数を削除します (Solaris スレッドについては、cond_destroy(3THR)を参照)。


プロトタイプ:
int	pthread_cond_destroy(pthread_cond_t *cv);

#include <pthread.h>

pthread_cond_t cv;
int ret;

/* 条件変数を削除する */
ret = pthread_cond_destroy(&cv); 

条件変数の記憶領域は解放されません。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を戻します。


EINVAL

cv で指定された値が無効です。

「呼び起こし忘れ」問題

pthread_cond_signal() または pthread_cond_broadcast() を呼び出すとき、スレッドが条件変数に関連する相互排他ロックを保持していないと「呼び起こし忘れ」(lost wake-up) という問題が生じることがあります。

「呼び起こし忘れ」 (lost wake-up) は次の場合に発生します。

「生産者 / 消費者」問題

「生産者 / 消費者」問題は、並行プログラミングに関する問題の中でも一般によく知られているものの 1 つです。この問題は次のように定式化されます。サイズが有限の 1 個のバッファと 2 種類のスレッドが存在します。一方のスレッドを生産者、もう一方のスレッドを消費者と呼びます。

生産者がバッファにデータを入れ、消費者がバッファからデータを取り出します。生産者は、バッファに空きができるまでデータを入れることができません。

特定の条件のシグナルを待つスレッドの待ち行列を条件変数で表すことにします。

例 4–11 では、そうした待ち行列として lessmore の 2 つを使用しています。less はバッファ内の未使用スロットを待つ生産者のための待ち行列で、more は情報が格納されたバッファスロットを待つ消費者のための待ち行列です。また、バッファが同時に複数のスレッドによってアクセスされないようにするために、相互排他ロック (mutex ロック) も使用しています。


例 4–11 「生産者 / 消費者」問題と条件変数


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() を呼び出して、「バッファ内に空きがある」を表す条件 less にシグナルが送られてくるのを待つスレッドの待ち行列に入ります。

同時に、pthread_cond_wait() の呼び出しによって、スレッドは mutex のロックを解除します。生産者スレッドは、条件が真になって消費者スレッドがシグナルを送ってくれるのを待ちます (詳細は、例 4–12 を参照してください)。条件にシグナルが送られてくると、less を待っている一番目のスレッドが呼び起こされます。しかし、そのスレッドは pthread_cond_wait() が戻る前に、mutex ロックを再び獲得する必要があります。

このようにして、バッファデータ構造への相互排他アクセスが保証されます。その後、生産者スレッドはバッファに本当に空きがあるか確認しなければなりません。空きがある場合は、最初の未使用スロットにデータを入れます。

このとき、バッファにデータが入れられるのを消費者スレッドが待っている可能性があります。そのスレッドは、条件変数 more で待ち状態となっています。生産者スレッドはバッファにデータを入れると、pthread_cond_signal() を呼び出して、待ち状態の最初の消費者を呼び起こします (待ち状態の消費者がいないときは、この呼び出しは無視されます)。

最後に、生産者スレッドは mutex ロックを解除して、他のスレッドがバッファデータ構造を操作できるようにします。


例 4–12 「生産者 / 消費者」問題 — 生産者


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++;

    /* 現在の状態: 「b->occupied < BSIZE かつ b->nextin はバッファ内
       の次の空きスロットのインデックス」または
       「b->occupied == BSIZE かつ b->nextin は次の 
        (占有されている) スロットのインデックス。これは
       消費者によってからにされる (例 b->nextin == b->nextout)」 */

    pthread_cond_signal(&b->more);

    pthread_mutex_unlock(&b->mutex);
}

上記のコード例の assert() 文の用法に注意してください。コンパイル時に NDEBUG を定義しなければ、assert() は次のように動作します。すなわち、引数が真 (0 以外の値) のときは何も行わず、引数が偽 (0) のときはプログラムを強制的に終了させます。このように、実行時に発生した問題をただちに指摘できる点がマルチスレッドプログラムに特に適しています。assert() はデバッグのための有用な情報も与えてくれます。

/* 現在の状態: ... で始まるコメント部分も assert() で表現した方がよいかもしれません。しかし、論理式で表現するには複雑すぎるので、ここでは文章で表現しています。

上記の assert() やコメント部分の論理式は、どちらも不変式の例です。不変式は、あるスレッドが不変式中の変数を変更している瞬間を除いて、プログラムの実行により偽の値に変更されない論理式です (もちろん assert の論理式は、どのスレッドがいつ実行した場合でも常に真であるべきです)。

不変式は非常に重要な手法です。プログラムテキストとして明示的に表現しなくても、プログラムを分析するときは不変式に置き換えて問題を考えることが大切です。

上記の生産者コード内のコメントで表現された不変式は、スレッドがそのコメントを含むコード部分を処理中には常に真となります。しかし、それを mutex_unlock() のすぐ後ろに移動すると、必ずしも常に真とはなりません。assert() のすぐ後ろに移動した場合は、真となります。

つまり、この不変式は、生産者または消費者がバッファの状態を変更しようとしているとき以外は、常に真となるような特性を表現しています。スレッドは mutex の保護下でバッファを操作しているとき、この不変式の値を一時的に偽にしてもかまいません。しかし、処理が完了したら不変式の値を再び真に戻さなければなりません。

例 4–13 は、消費者の処理です。この処理の流れは生産者の場合と対称的です。


例 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--;  
 
 /* 現在の状態:  「b->occupied> 0 かつ b->nextout はバッファ内の
       最初の占有されているスロットのインデックス」または「b->occupied == 0 
       かつ b-> nextout は次の (未使用) スロットのインデックス。これは
       生産者側によっていっぱいにされる 
       (例: b->nextout == b->nextin) 」 */

    pthread_cond_signal(&b->less);
    pthread_mutex_unlock(&b->mutex);

    return(item);
}

セマフォ

セマフォは、E.W. ダイクストラ (Dijkstra) が 1960 年代の終わりごろに考案したプログラミング手法です。ダイクストラのセマフォモデルは、鉄道線路の運行をモデル化したものです。一度に一本の列車しか走れない単線の鉄道線路を思い浮かべてください。

この鉄道線路を保護するのがセマフォです。列車は単線区間に入るとき、セマフォの状態が進行許可状態になるのを待たなければなりません。列車が単線区間に入るとセマフォの状態は、他の列車が単線区間に入るのを禁止する状態に変化します。単線区間から出る列車は、セマフォの状態を進行許可状態に戻して他の列車が単線区間に入ることができるようにしなければなりません。

コンピュータ内のセマフォは、単一の整数で表現されます。スレッドは進行が許可されるのを待ち、その後進行したことを知らせるためにセマフォに対して P 操作を実行します。

この操作をもう少し具体的に説明しましょう。スレッドは、セマフォの値が正になるのを待たなければなりません。処理を完了したセマフォは、V 操作を実行します。この操作は 1 を加えることでセマフォの値を変更します。ここで必ず守らなければならないことがあります。これらの各操作を不可分操作により行うことです。P 操作では、1 を引く直前のセマフォの値が正でなければなりません (結果的に、引いた後の値が負にならないことと、その値が引く前の値よりも 1 だけ小さいことが保証されます)。

P 操作と V 操作のどちらの演算操作でも干渉が生じないようにしなければなりません。たとえば、同じセマフォに対して 2 つの V 操作が同時に行われた場合、そのセマフォの新しい値は最初よりも 2 だけ大きくなっていなければなりません。

ダイクストラがオランダ人だったこともあり、PV の記号的な意味は現在ではほとんど忘れられています。参考までに、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 とともに使用される条件変数と同等の能力があります。多くの場合、条件変数よりも計数型セマフォを使用した方がコードが簡素化されます (後述の例を参照してください)。

しかし、mutex と条件変数をいっしょに使用すれば、自然と 1 つのまとまりとなり、プログラム内で保護されている場所が明確になります。セマフォは強力ですが、構造化されないあいまいな方法で使用してしまいがちです。「並行プログラミングにおける go to」と呼ばれることもあります。

計数型セマフォ

セマフォの概念は、0 以上の整数カウントです。通常は、リソースに対するアクセスの調整をはかる目的で、次のように使用されます。最初に、使用可能なリソースの数をセマフォに初期設定します。スレッドは、リソースが追加されると不可分操作的にカウントを 1 加算し、リソースが削除されると不可分操作的に 1 減算します。

この場合、セマフォの値を 1 減らそうとすると、スレッドはセマフォの値が 0 より大きくなるまでブロックされます。

表 4–7 セマフォに関するルーチン

操作 

参照先 

セマフォの初期化 

sem_init(3RT)

セマフォの加算 

sem_post(3RT)

セマフォの値によるブロック 

sem_wait(3RT)

セマフォの減算 

sem_trywait(3RT)

セマフォの削除 

sem_destroy(3RT)

セマフォは、その獲得と解放を同じスレッドで行う必要がないため、シグナルハンドラで行われているような非同期のイベント通知を実現できます。また、セマフォ自身が状態を持っているため、条件変数を使用する場合と違って相互排他ロックを獲得しなくても非同期で使用できます。ただし、セマフォは相互排他ロックほど効率的ではありません。

デフォルトでは、複数のスレッドがセマフォを待機している場合、ブロックを解除する順序はあらかじめ定義されていません。

セマフォは、使用する前に初期化されている必要がありますが、属性はありません。

セマフォの初期化

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;

/* セマフォの初期化 */
pshared = 0;
value = 1;
ret = sem_init(&sem, pshared, value); 

sema_init(3THR) は、sem が指すセマフォ変数を value の値に初期設定します。pshared の値が 0 なら、そのセマフォはプロセス間で共有できません。pshared の値が 0 以外なら、そのセマフォはプロセス間で共有できます。(Solaris スレッドについては、sema_init(3THR)を参照)。

複数のスレッドから同じセマフォを初期化してはいけません。

一度初期化したセマフォは他のスレッドが使用している可能性があるので、再初期化してはいけません。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

value の値が SEM_VALUE_MAX を超えています。


ENOSPC

そのセマフォを初期化するのに必要なリソースが使い果たされています。セマフォの制限 SEM_NSEMS_MAX に達しています。


EPERM

そのセマフォを初期化するのに必要な特権をそのプロセスがもっていません。

プロセス間スコープでセマフォを初期化する

pshared の値が 0 の場合は、そのプロセス内のスレッドだけがそのセマフォを使用できます。


#include <semaphore.h>

sem_t sem;
int ret;
int count = 4;

/* このプロセスでのみ使用 */
ret = sem_init(&sem, 0, count); 

プロセス間スコープでセマフォを初期化する

pshared の値が 0 以外の場合は、他のプロセスによってそのセマフォは共有されます。


#include <semaphore.h>

sem_t sem;
int ret;
int count = 4;

/* プロセス間で共有 */
ret = sem_init(&sem, 1, count);

名前付きセマフォ

sem_open(3RT)sem_getvalue(3RT)sem_close(3RT)sem_unlink(3RT) の各関数が、名前付きセマフォを開く、取得する、閉じる、削除するのにそれぞれ使用できます。sem_open() では、ファイルシステムの名前空間で名前が定義されたセマフォを生成できます。

名前付きセマフォはプロセス間で共有されるセマフォに似ていますが、pshared 値ではなくパス名で参照される点が異なります。

名前付きセマフォの詳細は、sem_open(3RT)sem_getvalue(3RT)sem_close(3RT)sem_unlink(3RT) のマニュアルページを参照してください。

セマフォの加算

sem_post(3RT)


プロトタイプ:
int	sem_post(sem_t *sem);

#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_post(&sem); /* セマフォを加算する */

sema_post(3THR) は、sem が指すセマフォの値を不可分操作によって 1 増やします。そのセマフォでブロックされているスレッドがある場合は、そのスレッドのうちの 1 つのスレッドがブロック解除されます (Solaris スレッドについては、sema_post(3THR)を参照)。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

sem が指すアドレスが正しくありません。

セマフォの値によるブロック

sem_wait(3RT)


プロトタイプ:
int	sem_wait(sem_t *sem);

#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_wait(&sem); /* セマフォの値の変化を待つ */

sema_wait(3THR) は、sem が指すセマフォの値が 0 より大きくなるまで呼び出しスレッドをブロックし、0 より大きくなったらセマフォの値を不可分操作によって 1 減らします。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

sem が指すアドレスが正しくありません。


EINTR

この関数にシグナルが割り込みを行いました。

セマフォの減算

sem_trywait(3RT)


プロトタイプ:
int	sem_trywait(sem_t *sem);

#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_trywait(&sem); /* セマフォの値の変化を待つ */

sem_trywait(3RT) は、sem が指すセマフォの値が 0 より大きい場合は不可分操作によって 1 減らします。この関数はブロックしない点を除いて、sem_wait() と同じ働きをします。つまり、失敗した場合にはすぐに戻ります。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を返します。


EINVAL

sem が指すアドレスが正しくありません。


EINTR

この関数にシグナルが割り込みを行いました。


EAGAIN

そのセマフォはすでにロックされているので、sem_trywait() でただちにロックできません。

セマフォの削除

sem_destroy(3RT)


プロトタイプ:
int	sem_destroy(sem_t *sem);

#include <semaphore.h>

sem_t sem;
int ret;

ret = sem_destroy(&sem); /* セマフォを削除する */

sem_destroy(3RT) は、sem が指すセマフォを削除します。セマフォの記憶領域は解放されません (Solaris スレッドについては、sem_destroy(3THR)を参照)。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、次の値を返します。


EINVAL

sem が指すアドレスが正しくありません。

「生産者 / 消費者」問題 — セマフォを使った例

例 4–14 のデータ構造は、条件変数による「生産者 / 消費者」問題のコード例 (例 4–11参照) のデータ構造と似ています。 2 つのセマフォでそれぞれ、いっぱいになったバッファ数と未使用バッファ数を表します。これらのセマフォは、未使用バッファができるまで生産者を待たせ、バッファがいっぱいになるまで消費者を待たせます。


例 4–14 「生産者 / 消費者」問題 — セマフォを使った例


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 を使用すべきですが、セマフォの使用例を示すために特に使用しています。


例 4–15 「生産者 / 消費者」問題 — 生産者


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);
}


例 4–16 「生産者 / 消費者」問題 — 消費者


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);
}

読み取り / 書き込みロック属性

読み取り / 書き込みロック によって、保護された共有リソースに対する並行する複数の読み取りと排他的な書き込みが可能になります。読み取り / 書き込みロックは単一の実体で、読み取りモードまたは 書き込み モードでロック可能です。リソースを変更するには、スレッドがまず排他書き込みロックを獲得する必要があります。すべての読み取りロックが開放されない限り、排他書き込みロックは許可されません。

データベースアクセスは、読み取り / 書き込みロックと同期させることができます。読み取り操作によってレコードの情報が変更されることはないので、読み取り / 書き込みロックではデータベースのレコードを並行して読み取ることができます。データベースを更新するときは、書き込み操作は排他的書き込みロックを獲得する必要があります。

デフォルトの読み取り / 書き込みロック属性を変更するときに、属性オブジェクトを宣言および初期化することができます。読み取り / 書き込みロック属性はアプリケーションのコードの開始位置にまとめて設定してある場合が多いので、その場所を素早く見つけて簡単に修正できます。ここで説明した読み取り / 書き込みロック属性を操作する関数を、次の表に示します。

Solaris スレッドに実装される読み取り / 書き込みロックついては、pthread に相当するものがある同期関数 — 読み取り / 書き込みロック を参照してください。

表 4–8 読み取り / 書き込みロック属性のルーチン

操作 

参照先 

読み取り / 書き込みロック属性の初期化 

pthread_rwlockattr_init(3THR)

読み取り / 書き込みロック属性の削除 

pthread_rwlockattr_destroy(3THR)

読み取り / 書き込みロック属性の設定  

pthread_rwlockattr_setpshared(3THR)

読み取り / 書き込みロック属性の取得 

pthread_rwlockattr_getpshared(3THR)

読み取り / 書き込みロック属性の初期化

pthread_rwlockattr_init(3THR)


#include <pthread.h>

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

pthread_rwlockattr_init(3THR) は、読み取り / 書き込みロック属性オブジェクト attr の、実装によって定義されたすべての属性を、デフォルト値に初期化します。

pthread_rwlockattr_init が呼び出すときに、初期化済みの読み取り / 書き込みロック属性オブジェクトを指定した場合、結果は保証されません。読み取り / 書き込みロック属性オブジェクトを使って初期化された読み取り / 書き込みロックは、属性オブジェクトに影響を与えるどんな関数(削除を含む)の影響も受けないためです。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


ENOMEM

読み取り / 書き込みロック 属性オブジェクトを初期化するためのメモリーが足りません。

読み取り / 書き込みロック属性の削除

pthread_rwlockattr_destroy(3THR)


#include <pthread.h>

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

pthread_rwlockattr_destroy(3THR) は、読み取り / 書き込みロック属性オブジェクトを削除します。削除したオブジェクトを、pthread_rwlockattr_init() の呼び出しによって再び初期化する前に使った場合、その結果は未定義です。実装によっては、pthread_rwlockattr_destroy() は、attr が参照するオブジェクトに不正な値を設定する場合もあります。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

attr が示す値は無効です。

読み取り / 書き込みロック属性の設定

pthread_rwlockattr_setpshared(3THR)


#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t  *attr, 
                                                         int *pshared);

pthread_rwlockattr_setpshared(3THR) は、プロセス共有の読み取り / 書き込みロック属性を設定します。


PTHREAD_PROCESS_SHARED

読み取り / 書き込みロックが割り当てられているメモリーにアクセスできるすべてのスレッドに、読み取り / 書き込みロックの操作を許可します。複数のプロセスによって共有されているメモリに置かれた読み取り / 書き込みロックに対しても有効です。


PTHREAD_PROCESS_PRIVATE

読み取り / 書き込みロックを操作できるのは、そのロックを初期化したスレッドと同じプロセス内で作成されたスレッドだけです。異なるプロセスのスレッドから読み取り / 書き込みロックを操作しようとした場合、その結果は未定義です。プロセス共有の属性のデフォルト値は、PTHREAD_PROCESS_PRIVATE です。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

attr または pshared が示す値は無効です。

読み取り / 書き込みロック属性の取得

pthread_rwlockattr_getpshared(3THR)


#include <pthread.h>

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t  *attr, 
                                                         int *pshared);

pthread_rwlockattr_getpshared(3THR) は、プロセス共有の読み取り / 書き込みロック属性を取得します。

pthread_rwlockattr_getpshared() は、attr が参照する初期化済みの属性オブジェクトから、プロセス共有の属性の値を取得します。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

attr または pshared が示す値は無効です。

読み取り / 書き込みロックの使用

読み取り / 書き込みロックの属性を設定したあとに、読み取り / 書き込みロックそのものを初期化します。次の関数を使って、読み取り / 書き込みロックを初期化または削除したり、ロックまたはロック解除したり、ロックを試みたりできます。ここで説明した読み取り / 書き込みロック属性を操作する関数を、次の表に示します。

表 4–9 読み取り / 書き込みロック属性のルーチン

操作 

参照先 

読み取り/書き込みロックの初期化 

pthread_rwlock_init(3THR)

読み取り/書き込みロックの読み取りロック 

pthread_rwlock_rdlock(3THR)

非ブロック読み取り/書き込みロックの読み取りロック  

pthread_rwlock_tryrdlock(3THR)

読み取り / 書き込みロックの書き込みロック 

pthread_rwlock_wrlock(3THR)

非ブロック読み取り/書き込みロックの書き込みロック 

pthread_rwlock_trywrlock(3THR)

読み取り/書き込みロックの解除 

pthread_rwlock_unlock(3THR)

読み取り/書き込みロックの削除 

pthread_rwlock_destroy(3THR)

読み取り / 書き込みロックの初期化

pthread_rwlock_init(3THR)


#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock, 
                                const pthread_rwlockattr_t *attr);

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlock_init(3THR) により、attr が参照する属性を使用して、rwlock が参照する読み取り / 書き込みロックを初期化します。attrNULL の場合、デフォルトの読み取り / 書き込みロック属性が使われます。この場合の結果は、デフォルトの読み取り / 書き込みロック属性オブジェクトのアドレスを渡す場合と同じです。いったん初期化したロックは、繰り返して使用するために再び初期化する必要はありません。初期化が成功すると、読み取り / 書き込みロックは初期化され、ロックが解除された状態になります。初期化済みの読み取り / 書き込みロックを指定して、pthread_rwlock_init() を呼び出した場合、その結果は不定です。最初に初期化しないで読み取り / 書き込みロックを使用した場合も、その結果は不定です。Solaris スレッドについては、rwlock_init(3THR)を参照してください。

デフォルトの読み取り / 書き込みロック属性を使用するのであれば、PTHREAD_RWLOCK_INITIALIZER というマクロを使用して、静的に割り当てられている読み取り / 書き込みロックを初期化できます。この場合の結果は、パラメータ attrNULL を指定して pthread_rwlock_init() を呼び出し、動的に初期化したときと同じです。ただし、エラーチェックが実行されません。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。

pthread_rwlock_init() が正常に終了しなかった場合、rwlock は初期化されず、rwlock の内容は未定義です。


EINVAL

attr または rwlock が示す値は無効です。

読み取り / 書き込みロックの読み取りロック

pthread_rwlock_rdlock(3THR)


#include <pthread.h>

int  pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );

pthread_rwlock_rdlock(3THR) は、rwlock が参照する読み取り / 書き込みロックに読み取りロックを適用します。書き込みがロックを保持せず、読み取り / 書き込みロックでブロックされている書き込みもない場合は、呼び出しスレッドは読み取りロックを獲得します。書き込みがロックを保持せず、ロック待ちの書き込みがある場合は、呼び出しスレッドが読み取りロックを獲得するかどうかは不定です。書き込みが読み取り / 書き込みロックを保持している場合は、呼び出しスレッドは読み取りロックを獲得しません。読み取りロックが獲得されない場合、呼び出しスレッドは読み取りロックを獲得するまでブロックします。つまり、呼び出しスレッドは、pthread_rwlock_rdlock() から戻り値を取得しません。呼び出し時に、呼び出しスレッドが rwlock に書き込みロックを保持する場合、その結果は不定です。

書き込み側がいつまでもロックを獲得できない事態を避けるために、書き込みが読み取りに優先するように実装することが許されています。たとえば、Solaris スレッドの実装では、書き込みが読み取りに優先します。rw_rdlock(3THR) を参照してください。

スレッドは、rwlock に複数の並行的な読み取りロックを保持できます。つまり、pthread_rwlock_rdlock() の呼び出しが n 回成功します。この場合、スレッドは同数の読み取りロック解除を行わなければなりません。つまり、pthread_rwlock_unlock()n 回呼び出さなければなりません。

pthread_rwlock_rdlock() が、初期化されていない読み取り / 書き込みロックに対して呼び出された場合、その結果は不定です。

読み取りのための読み取り / 書き込みロックを待っているスレッドにシグナルが送られた場合、スレッドはシグナルハンドラから戻ると、見かけ上割り込みがなかった場合と同様に、読み取りのための読み取り / 書き込みロック待ちを再開します。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

attr または rwlock が示す値は無効です。

非ブロック読み取り / 書き込みロックの読み取りロック

pthread_rwlock_tryrdlock(3THR)


#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_tryrdlock(3THR) は、pthread_rwlock_rdlock() と同様に読み取りロックを適用します。ただし、いずれかのスレッドが rwlock に書き込みロックを保持しているか、rwlock で書き込みスレッドがブロックされている場合、この関数は失敗します。Solaris スレッドについては、rw_tryrdlock(3THR) を参照してください。

戻り値

rwlock が参照する読み取り / 書き込みロックオブジェクトに対する読み取りロックが獲得された場合、戻り値は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EBUSY

書き込みが読み取り / 書き込みロックを保持しているか、読み取り / 書き込みロックで書き込みスレッドがブロックされているため、読み取りのための読み取り / 書き込みロックを獲得できません。

読み取り / 書き込みロックの書き込みロック

pthread_rwlock_wrlock(3THR)


#include <pthread.h>

int  pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );

pthread_rwlock_wrlock(3THR) は、rwlock が参照する読み取り / 書き込みロックに書き込みロックを適用します。ほかのスレッド (読み取り側または書き込み側) が rwlock という読み取り / 書き込みロックを保持していない場合、呼び出しスレッドは書き込みロックを獲得します。これ以外の場合、スレッドは、ロックを獲得するまでブロックされます。つまり、pthread_rwlock_wrlock() の呼び出しから戻りません。呼び出し時に、呼び出しスレッドが読み取り / 書き込みロックを保持している場合 (読み取りロックと書き込みロックのどちらでも) の結果は不定です。

書き込み側がいつまでもロックを獲得できない事態を避けるために、書き込みが読み取りに優先するように実装することが許されています。たとえば、Solaris スレッドの実装では、書き込みが読み取りに優先します。rw_wrlock(3THR)を参照してください。

pthread_rwlock_wrlock() が、初期化されていない読み取り / 書き込みロックに対して呼び出された場合、その結果は不定です。

書き込みのための読み取り / 書き込みロックを待っているスレッドにシグナルが送られた場合、スレッドはシグナルハンドラから戻ると、見かけ上割り込みがなかった場合と同様に、書き込みのための読み取り / 書き込みロック待ちを再開します。

戻り値

rwlock が参照する読み取り / 書き込みロックオブジェクトに対する書き込みロックが獲得された場合、pthread_rwlock_rwlock()は 0 を返します。それ以外の戻り値は、エラーが発生したことを示します。

非ブロック読み取り / 書き込みロックの書き込みロック

pthread_rwlock_trywrlock(3THR)


#include <pthread.h>

int pthread_rwlock_trywrlock(pthread_rwlock_t  *rwlock);

pthread_rwlock_trywrlock(3THR) は、pthread_rwlock_wrlock() と同様に書き込みロックを適用します。ただし、いずれかのスレッドが現時点で rwlock (読み取り用または書き込み用) を保持している場合、この関数は失敗します。Solaris スレッドについては、rw_trywrlock(3THR) を参照してください。

pthread_rwlock_trywrlock() が、初期化されていない読み取り / 書き込みロックに対して呼び出された場合、その結果は不定です。

書き込みのための読み取り / 書き込みロックを待っているスレッドにシグナルが送られた場合、スレッドはシグナルハンドラから戻ると、見かけ上割り込みがなかった場合と同様に、書き込みのための読み取り / 書き込みロック待ちを再開します。

戻り値

rwlock が参照する読み取り / 書き込みロックオブジェクトの書き込みロックを獲得した場合、戻り値は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EBUSY

読み取りまたは書き込みでロック済みのため、書き込みのための読み取り / 書き込みロックを獲得できません。

読み取り / 書き込みロックの解除

pthread_rwlock_unlock(3THR)


#include <pthread.h>

int pthread_rwlock_unlock(pthread_rwlock_t  *rwlock);

pthread_rwlock_unlock(3THR) は、rwlock が参照する読み取り / 書き込みロックオブジェクトに保持されたロックを解放します。呼び出しスレッドが rwlock という読み取り / 書き込みロックを保持していない場合、その結果は不定です。Solaris スレッドについては、rw_unlock(3THR) を参照してください。

pthread_rwlock_unlock() を呼び出して読み取り / 書き込みロックオブジェクトから読み取りオブジェクトを解放しても、この読み取り / 書き込みロックオブジェクトに他の読み取りロックが保持されている場合、読み取り / 書き込みロックオブジェクトは読み取りにロックされたままになります。pthread_rwlock_unlock() が、呼び出しスレッドによる最後の読み取りロックを解放すると、呼び出しスレッドはこのオブジェクトの所有者でなくなります。pthread_rwlock_unlock() がこの読み取り / 書き込みロックオブジェクトの最後の読み取りロックを解放すると、読み取り / 書き込みロックオブジェクトはロックが解除され、所有者のない状態になります。

pthread_rwlock_unlock() を呼び出し、読み取り / 書き込みロックオブジェクトから書き込みオブジェクトを解放すると、読み取り / 書き込みロックオブジェクトはロックが解除され、所有者のない状態になります。

pthread_rwlock_unlock() を呼び出した結果として読み取り / 書き込みロックオブジェクトがロック解除されたときに、複数のスレッドが書き込みのための読み取り / 書き込みロックオブジェクトの獲得を待っている場合は、スケジューリング方針を使用して、書き込みのための読み取り / 書き込みロックオブジェクトを獲得するスレッドが決定されます。また、複数のスレッドが読み取りのための読み取り / 書き込みロックオブジェクトの獲得を待っている場合も、スケジューリング方針を使用して、読み取りのための読み取り / 書き込みロックオブジェクトを獲得するスレッドの順番が決定されます。さらに、複数のスレッドが読み取りロックと書き込みロック両方のために rwlock にブロックされている場合は、読み取り側と書き込み側のどちらが先にロックを獲得するのかは規定されていません。

pthread_rwlock_unlock() が、初期化されていない読み取り / 書き込みロックに対して呼び出された場合、その結果は不定です。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。

読み取り / 書き込みロックの削除

pthread_rwlock_destroy(3THR)


#include <pthread.h>

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER;

pthread_rwlock_destroy(3THR) は、rwlock が示す読み取り / 書き込みロックオブジェクトを削除し、このロックで使用されていたリソースを解放します。削除したオブジェクトを、pthread_rwlock_init() の呼び出しによって再び初期化する前に使用した場合、その結果は不定です。実装によっては、pthread_rwlock_destroy() は、rwlock が参照するオブジェクトに不正な値を設定する場合もあります。いずれかのスレッドが rwlock を保持しているときに pthread_rwlock_destroy() を呼び出した場合の結果は不定です。初期化されていない読み取り / 書き込みロックを削除しようとした場合に発生する動作も不定です。また、削除された読み取り / 書き込みロックオブジェクトは、再度 pthread_rwlock_init() で初期化できます。削除した読み取り / 書き込みロックオブジェクトを初期化せずに参照した場合も不定です。Solaris スレッドについては、rwlock_destroy(3THR) を参照してください。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。


EINVAL

attr または rwlock が示す値は無効です。

プロセスの境界を越えた同期

今までに説明した 4 種類の同期プリミティブは、プロセスの境界を越えて使用するように設定できます。具体的には次のようにします。まず、その同期変数の領域が共有メモリーに確保されるようにし、次に該当する init() ルーチンを呼び出しますが、これはそのプリミティブをプロセス間共有属性で初期化した後に行います。

「生産者 / 消費者」問題の例

例 4–17 は、前述の「生産者 / 消費者」問題の生産者と消費者をそれぞれ別のプロセスで表現したものです。メインルーチンは、0 に初期化されたメモリーを自分のアドレス空間にマッピングし、それを子プロセスと共有します。

子プロセスが 1 つ生成され、消費者の処理が実行されます。親プロセスは生産者の処理を実行します。

この例では、生産者と消費者を呼び出す各駆動ルーチンも示しています。producer_driver()stdin から文字を読み込み、producer() を呼び出します。consumer_driver()consumer() を呼び出して文字を受け取り、stdout に書き出します。

例 4–17 のデータ構造は、条件変数による「生産者 / 消費者」の例のデータ構造 (例 4–4を参照) と同じです。2 つのセマフォでそれぞれ、いっぱいになったバッファ数と未使用バッファ数を表します。これらのセマフォは、未使用バッファができるまで生産者を待たせ、バッファがいっぱいになるまで消費者を待たせます。


例 4–17 プロセスの境界を越えた同期


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);
    }
}

スレッドライブラリによらないプロセス間ロック

スレッドライブラリを使用しないでプロセス間ロックを適用することができます。ただし、通常はこの方法を使用しないでください。詳細は、プロセス間での LWP の使用を参照してください。

プリミティブの比較

スレッドで使われる最も基本的な同期プリミティブは、相互排他ロックです。相互排他ロックは、メモリー使用量と実行時間の両面で最も効率的な機構です。相互排他ロックの主要目的は、リソースへのアクセスを直列化することです。

相互排他ロックに次いで効率的なプリミティブは、条件変数です。条件変数の主要目的は、状態の変化に基づいてスレッドをブロックすることです。つまり、スレッド待ち機能の提供です。条件変数でスレッドをブロックする場合は、その前に相互排他ロックを獲得しなければなりません。また、pthread_cond_wait() から戻った後に相互排他ロックを解除しなければいけません。また、対応する pthread_cond_signal() 呼び出しまで状態の変更が行われる間、相互排他ロックを保持しておかなければなりません。

セマフォは、条件変数より多くのメモリーを消費しますが、状況によっては条件変数よりも簡単に使用できます。セマフォ変数は、制御でなく状態に基づいて機能するからです。また、ロックのように保持するという概念もありません。スレッドをブロックしているセマフォに対して、どのスレッドもセマフォの値を 1 増やすことができます。

読み取り / 書き込みロックを使用すると、保護されたリソースに対する、並行する複数の読み取り操作や排他的な書き込み操作ができます。読み取り / 書き込みロックは単一の実体で、読み取りモードまたは 書き込み モードでロック可能です。リソースを変更するには、スレッドがまず排他書き込みロックを獲得する必要があります。すべての読み取りロックが開放されない限り、排他書き込みロックは許可されません。