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

第 9 章 Solaris スレッドを使ったプログラミング

この章では、Solaris スレッドと POSIX スレッドのアプリケーションプログラミングインタフェース (API) を比較し、POSIX スレッドにはない Solaris の機能について説明します。

Solaris スレッドと POSIX スレッドの API の比較

Solaris スレッド API と POSIX スレッド (pthread) API は、どちらもアプリケーションソフトウェアに並列性を導入する手段です。どちらの API もそれ自体で完結したものですが、Solaris スレッドの関数と pthread の関数を同じプログラムの中で併用することもできます。

ただし、2 つの API は完全に一致しているわけではありません。Solaris スレッドは pthread にない関数をサポートしていて、pthread には Solaris インタフェースでサポートされない関数が含まれています。同じ関数については、機能が実質的に同じでも使用する引数が異なることがあります。

2 つの API を組み合わせて使用すれば、それぞれ他方にない機能を補い合うことができます。また、同じシステムで、Solaris スレッドだけを使用するアプリケーションを実行する一方で、pthread だけを使用するアプリケーションを実行することもできます。

API の主な相違点

Solaris スレッドと pthread は、API の動作や構文も非常によく似ています。主な相違点を表 9-1 に示します。

表 9-1 Solaris スレッドと pthread の相違点

Solaris スレッド (libthread) に固有

POSIX スレッド (libpthread) に固有

スレッド関数名の接頭辞が thr_ で、セマフォ関数名の接頭辞が sema_

スレッド関数名の接頭辞が pthread_ で、セマフォ関数名の接頭辞が sem_

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

属性オブジェクト (Solaris の多くの引数やフラグは pthread の属性オブジェクトと同等である)

デーモンスレッドが生成可能 

取り消しセマンティクス 

スレッドの停止と再開 

スケジューリング方針 

並行度の設定 (新しい LWP の要求) と並行度の取得 

 

関数比較表

表 9-2 は、Solaris スレッドの関数と pthread の関数を比較対照したものです。なお、Solaris スレッドの関数と pthread の関数が本質的に同じものとして並記されている場合でも、その引数は異なっていることがあります。

pthread または Solaris スレッドの側に相当するインタフェースがない場合は、「-」が記入されています。pthread 欄の項目で「POSIX 1003.4」または「POSIX.4」が付記されているものは、POSIX 規格のリアルタイムの仕様の一部で pthread の一部ではありません。

表 9-2 Solaris スレッドと POSIX pthread の比較

Solaris スレッド (libthread)

pthread (libpthread)

thr_create()

pthread_create()

thr_exit()

pthread_exit()

thr_join()

pthread_join()

thr_yield()

sched_yield() POSIX.4

thr_self()

pthread_self()

thr_kill()

pthread_kill()

thr_sigsetmask()

pthread_sigmask()

thr_setprio()

pthread_setschedparam()

thr_getprio()

pthread_getschedparam()

thr_setconcurrency()

pthread_setconcurrency()

thr_getconcurrency()

pthread_getconcurrency()

thr_suspend()

thr_continue()

thr_keycreate()

pthread_key_create()

pthread_key_delete()

thr_setspecific()

pthread_setspecific()

thr_getspecific()

pthread_getspecific()

pthread_once()

pthread_equal()

pthread_cancel()

pthread_testcancel()

pthread_cleanup_push()

pthread_cleanup_pop()

pthread_setcanceltype()

pthread_setcancelstate()

mutex_lock()

pthread_mutex_lock()

mutex_unlock()

pthread_mutex_unlock()

mutex_trylock()

pthread_mutex_trylock()

mutex_init()

pthread_mutex_init()

mutex_destroy()

pthread_mutex_destroy()

cond_wait()

pthread_cond_wait()

cond_timedwait()

pthread_cond_timedwait()

cond_signal()

pthread_cond_signal()

cond_broadcast()

pthread_cond_broadcast()

cond_init()

pthread_cond_init()

cond_destroy()

pthread_cond_destroy()

rwlock_init()

pthread_rwlock_init()

rwlock_destroy()

pthread_rwlock_destroy()

rw_rdlock()

pthread_rwlock_rdlock()

rw_wrlock()

pthread_rwlock_wrlock()

rw_unlock()

pthread_rwlock_unlock()

rw_tryrdlock()

pthread_rwlock_tryrdlock()

rw_trywrlock()

pthread_rwlock_trywrlock()

pthread_rwlockattr_init()

pthread_rwlockattr_destroy()

pthread_rwlockattr_getpshared()

pthread_rwlockattr_setpshared()

sema_init()

sem_init() POSIX 1003.4

sema_destroy()

sem_destroy() POSIX 1003.4

sema_wait()

sem_wait() POSIX 1003.4

sema_post()

sem_post() POSIX 1003.4

sema_trywait()

sem_trywait() POSIX 1003.4

fork1()

fork()

pthread_atfork()

fork() (複数スレッドコピー)

pthread_mutexattr_init()

pthread_mutexattr_destroy()

cond_init()type() 引数

pthread_mutexattr_setpshared()

pthread_mutexattr_getpshared()

pthread_mutex_attr_settype()

pthread_mutex_attr_gettype()

pthread_condattr_init()

pthread_condattr_destroy()

cond_init()type() 引数

pthread_condattr_setpshared()

pthread_condattr_getpshared()

pthread_attr_init()

pthread_attr_destroy()

thr_create() の THR_BOUND フラグ

pthread_attr_setscope()

pthread_attr_getscope()

pthread_attr_setguardsize()

pthread_attr_getguardsize()

thr_create()stack_size() 引数

pthread_attr_setstacksize()

pthread_attr_getstacksize()

thr_create()stack_addr() 引数

pthread_attr_setstackaddr()

pthread_attr_getstackaddr()

thr_create() の THR_DETACH フラグ

pthread_attr_setdetachstate()

pthread_attr_getdetachstate()

pthread_attr_setschedparam()

pthread_attr_getschedparam()

pthread_attr_setinheritsched()

pthread_attr_getinheritsched()

pthread_attr_setsschedpolicy()

pthread_attr_getschedpolicy()

この章で説明する Solaris スレッドの関数を使用するには、リンクで Solaris スレッドライブラリ (-lthread) を指定しなければなりません。

Solaris スレッドと pthread で機能的にほとんど変わらない場合は (関数名と引数が違うとしても)、正しいインクルードファイルと関数プロトタイプを示した簡単な例を挙げているだけです。Solaris スレッドで戻り値が記述されていないものについては、『man pages section 3』から該当するページを探して、その関数の戻り値を調べてください。

Solaris 関連の関数の詳細は、pthread の関連マニュアルで類似した名前の関数を調べてください。

Solaris スレッドの関数で pthread にない機能をもつものについて、詳しく説明しています。

Solaris スレッドに固有の関数

スレッド実行の停止

thr_suspend(3T)

thr_suspend(3T) は、target_thread で指定したスレッドの実行をただちに停止させます。thr_suspend() が正常終了した時点で、指定のスレッドは実行状態ではありません。

停止しているスレッドに対して再度 thr_suspend() を発行しても効果はありません。停止しているスレッドをシグナルで呼び起こすことはできません。スレッドが実行を再開するまでシグナルは保留状態のままです。


#include <thread.h>

int thr_suspend(thread_t tid);

次の例では、pthread で定義されている pthread_t tid と Solaris スレッドの thread_t tid が同じです。tid 値は、代入によっても型変換によっても使用できます。


thread_t tid; /* thr_create() からの tid */

/* pthread_create() で生成されたスレッドからの */
/* Solaris tid に相当する pthread */
pthread_t ptid;	

int ret;

ret = thr_suspend(tid);

/* 型変換で pthread ID 変数を使用する */
ret = thr_suspend((thread_t) ptid);	

戻り値

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


ESRCH

現在のプロセスに tid が存在しません。

停止しているスレッドの再開

thr_continue(3T)

thr_continue(3T) は、停止しているスレッドの実行を再開します。再開したスレッドに対して再度 thr_continue() を発行しても効果はありません。


#include <thread.h>

int thr_continue(thread_t tid);

停止しているスレッドがシグナルで呼び起こされることはありません。送られたシグナルは、そのスレッドが thr_continue() で再開されるまで保留されます。

pthread で定義されている pthread_t tid と Solaris スレッドの thread_t tid が同じです。tid 値は、代入によっても型変換によっても使用できます。


thread_t tid; /* thr_create() からの tid */

/* pthread_create() で生成されたスレッドからの Solaris tid に */
/* 相当する pthread */
pthread_t ptid;	

int ret;

ret = thr_continue(tid);

/* 型変換で pthread ID 変数を使用する */
ret = thr_continue((thread_t) ptid)	

戻り値

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


ESRCH

現在のプロセスに tid が存在しません。

スレッドの並行度の設定

Solaris スレッドは、デフォルトでは、非結合スレッドの実行に使用するシステム実行リソース (LWP) を、有効なスレッドの実際の数に合わせて調整しようとします。Solaris スレッドパッケージは完璧な判定はできなくても、少なくともプロセスが実行を継続できるようにします。

どれだけの数の非結合スレッド (実行するコードまたはシステムコール) を同時に有効すべきか見当がつく場合は、thr_setconcurrency() で指定してください。使用されているスレッドの数を取得するには、thr_getconcurrency() を使用してください。

thr_setconcurrency(3T)

thr_setconcurrency(3T) は、アプリケーションの中で必要とする並行度の目標値をシステムに指示します。システムは、十分な数のスレッドを有効にして、プロセスが実行を継続できるようにします。


#include <thread.h>


int new_level;
int ret;


ret = thr_setconcurrency(new_level);

プロセス内の非結合スレッドを同時に有効にする必要があるかどうかは、状況によって変化します。スレッドシステムのデフォルト設定では、システムリソースを節約することを前提にして、プロセスに必要な数のスレッドを有効にします。また、並行度を小さくしすぎてプロセスがデッドロックに陥るといった事態が生じないように並行度も調整されます。

このようなデフォルトの設定では効果的な並行度が得られない場合、アプリケーション側は thr_setconcurrency()new_level の指定で、スレッドシステムに並行度の目標値を指示できます。

同時に有効になるスレッドの実際の数は、new_level より大きいことも小さいこともあります。

計算を目的とするスレッドが複数存在するアプリケーションでは、thr_setconcurrency() によって実行リソースの並行度を調整しておかないと、実行可能なすべてのスレッドのスケジューリングが適切に行われないことがあります。

thr_create()THR_NEW_LWP フラグでも並行度に影響を与えることができます。これには、現在の並行度を 1 だけ大きくする効果があります。

戻り値

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


EAGAIN

指定の並行度ではシステムリソースの制限を超えます。


EINVAL

new_level の値が負です。

スレッドの並行度の取得

thr_getconcurrency(3T)

thr_getconcurrency(3T) は、thr_sgetconcurrency() で設定された並行度の現在値を取得します。同時に有効になっているスレッドの実際の数は、この値より大きいことも小さいこともあります。


#include <thread.h>

int thr_getconcurrency(void)

戻り値

thr_getconcurrency() は、常に並行度の現在の値を戻します。

pthread に相当するものがある同期関数 - 読み取り / 書き込みロック

読み取り / 書き込みロックを使用すると、同時に書き込み操作ができるスレッドを 1 つだけに制限する一方、読み取り操作は同時に複数のスレッドからできるようになります。

すでに読み取りロックを保持しているスレッドがある場合、他のスレッドがさらに読み取りロックを獲得できますが、書き込みロックを獲得するときは待たなければなりません。すでに書き込みロックを保持しているスレッドがある場合、あるいは書き込みロックの獲得を待っているスレッドがある場合、他のスレッドは読み取りと書き込みのどちらのロックを獲得するときも待たなければなりません。

読み取り / 書き込みロックは、相互排他ロックよりも低速です。しかし、書き込みの頻度が低く、かつ多数のスレッドから並行的に読み取られるようなデータを保護するときに特に性能を改善します。

現在のプロセス内のスレッドと他のプロセス内のスレッドの間で、読み取り / 書き込みロックを使って同期をとる場合は、連携するそれらのプロセスの間で共有される書き込み可能なメモリーに、読み取り / 書き込みロックの領域を確保し (mmap(2) のマニュアルページを参照)、その読み取り / 書き込みロックをプロセス間同期用に初期化します。

複数のスレッドが読み取り / 書き込みロックを待っている場合のロックの獲得順序は、特に指定しなければ不定です。ただし、書き込み側がいつまでもロックを獲得できないような事態を回避するため、Solaris スレッドパッケージでは書き込み側が読み取り側より優先されます。

読み取り / 書き込みロックは、使用する前に初期化する必要があります。

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

rwlock_init(3T)


#include <synch.h>  (または #include <thread.h>)

int rwlock_init(rwlock_t *rwlp, int type, void * arg);

rwlock_init(3T) は、rwlp が指す読み取り / 書き込みロックを初期化してロック解除状態に設定します。type には次のいずれかを指定できます (arg は現在は無視されます)。(POSIX スレッドについては、「pthread_rwlock_init(3T)」を参照)。

複数のスレッドから同じ読み取り / 書き込みロックを同時に初期化してはいけません。0 に初期化したメモリーに領域を確保することによって、読み取り / 書き込みロックを初期化することもできます。その場合は、typeUSYNC_THREAD を指定したものとみなされます。一度初期化した読み取り / 書き込みロックは、他のスレッドで使われている可能性があるので再初期化してはいけません。

プロセス内スコープでの読み取り / 書き込みロックの初期化


#include <thread.h>

rwlock_t rwlp;
int ret;

/* このプロセスの中だけで使用する */
ret = rwlock_init(&rwlp, USYNC_THREAD, 0); 

プロセス間スコープでの読み取り / 書き込みロックの初期化


#include <thread.h>

rwlock_t rwlp;
int ret;

/* すべてのプロセスの間で使用する */
ret = rwlock_init(&rwlp, USYNC_PROCESS, 0); 

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp または arg が無効なアドレスを指しています。

読み取りロックの獲得

rw_rdlock(3T)


#include <synch.h> (または #include <thread.h>)

int rw_rdlock(rwlock_t *rwlp);

rw_rdlock(3T) は、rwlp が指す読み取り / 書き込みロックの読み取りロックを獲得します。指定した読み取り / 書き込みロックが書き込み用にすでにロックされている場合、呼び出しスレッドは書き込みロックが解放されるまでブロックされます。そうでなければ、読み取りロックを獲得します。(POSIX スレッドについては、「pthread_rwlock_rdlock(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が無効なアドレスを指しています。

読み取りロックの獲得 (ブロックなし)

rw_tryrdlock(3T)


#include <synch.h>  (または #include <thread.h>)

int rw_tryrdlock(rwlock_t *rwlp);

rw_tryrdlock(3T) は、rwlp が指す読み取り / 書き込みロックの読み取りロックを獲得しようとします。指定した読み取り / 書き込みロックが書き込み用にすでにロックされている場合は、エラーを戻します。そうでなければ、呼び出しスレッドは読み取りロックを獲得します。(POSIX スレッドについては、「pthread_rwlock_tryrdlock(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が無効なアドレスを指しています。


EBUSY

rwlp が指す読み取り / 書き込みロックがすでにロックされています。

書き込みロックの獲得

rw_wrlock(3T)


#include <synch.h>  (または #include <thread.h>)

int rw_wrlock(rwlock_t *rwlp);

rw_wrlock(3T) は、rwlp が指す読み取り / 書き込みロックの書き込みロックを獲得します。指定した読み取り / 書き込みロックが、読み取りまたは書き込み用にすでにロックされている場合、呼び出しスレッドは、すべての読み取りロックと書き込みロックが解放されるまでブロックされます。読み取り / 書き込みロックの書き込みロックを保持できるスレッドは一度に 1 つに限られます。(POSIX スレッドについては、「pthread_rwlock_wrlock(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が不当なアドレスを指しています。

書き込みロックの獲得

rw_trywrlock(3T)


#include <synch.h>  (または #include <thread.h>)

int rw_trywrlock(rwlock_t *rwlp);

rw_trywrlock(3T) は、rwlp が指す読み取り / 書き込みロックの書き込みロックを獲得しようとします。指定した読み取り / 書き込みロックが、読み取りまたは書き込み用にすでにロックされている場合はエラーを戻します。(POSIX スレッドについては、「pthread_rwlock_trywrlock(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が無効なアドレスを指しています。


EBUSY

rwlp が指す読み取り / 書き込みロックがすでにロックされています。

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

rw_unlock(3T)


#include <synch.h>  (または #include <thread.h>)

int rw_unlock(rwlock_t *rwlp);

rw_unlock(3T) は、rwlp が指す読み取り / 書き込みロックのロックを解除します。解除の対象となる読み取り / 書き込みロックは、ロックされていて、呼び出しスレッドが読み取り用または書き込み用に保持しているものでなければなりません。その読み取り / 書き込みロックが使用可能になるのを待っているスレッドが他にある場合は、そのスレッドのうちの 1 つがブロック解除されます。(POSIX スレッドについては、「pthread_rwlock_unlock(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が無効なアドレスを指しています。

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

rwlock_destroy(3T)


#include <synch.h>  (または #include <thread.h>)

int rwlock_destroy(rwlock_t *rwlp);

rwlock_destroy(3T) は、rwlp が指す読み取り / 書き込みロックを削除します。読み取り / 書き込みロックの記憶領域は解放されません。(POSIX スレッドについては、「pthread_rwlock_destroy(3T)」を参照)。

戻り値

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


EINVAL

引数が無効です。


EFAULT

rwlp が無効なアドレスを指しています。

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

例 9-1 では、銀行口座に関する処理で読み取り / 書き込みロックを使用しています。口座残高に対して複数のスレッドが並行的に読み取り専用アクセスできますが、書き込みは 1 つのスレッドだけに制限されます。get_balance() 関数中のロックは、当座預金の残高 (checking_balance) と普通預金の残高 (saving_balance) を合計する演算が、原子操作によって行われることを保証するため必要です。


例 9-1 銀行口座の読み取り / 書き込み


rwlock_t account_lock;
float checking_balance = 100.0;
float saving_balance = 100.0;
...
rwlock_init(&account_lock, 0, NULL);
...

float
get_balance() {
    float bal;

    rw_rdlock(&account_lock);
    bal = checking_balance + saving_balance;									
    rw_unlock(&account_lock);
    return(bal);
}

void
transfer_checking_to_savings(float amount) {
    rw_wrlock(&account_lock);
    checking_balance = checking_balance - amount;
    saving_balance = saving_balance + amount;
    rw_unlock(&account_lock);
}

pthread に相当するものがある Solaris スレッドの関数

操作 

参照先 

スレッドの生成 

「thr_create(3T)」

最小のスタックの大きさの取得 

「thr_min_stack(3T)」

スレッド識別子の取得 

「thr_self(3T)」

スレッドの実行明け渡し 

「thr_yield(3T)」

シグナルのスレッドへの送信 

「thr_kill(3T)」

呼び出しスレッドのシグナルマスクのアクセス 

「thr_sigsetmask(3T)」

スレッドの終了 

「thr_exit(3T)」

スレッドの終了待ち 

「thr_join(3T)」

スレッド固有データ用キーの作成 

「thr_keycreate(3T)」

スレッド固有データ用キーの設定 

「thr_setspecific(3T)」

スレッド固有データ用キーの取得 

「thr_getspecific(3T)」

スレッド優先順位の設定 

「thr_setprio(3T)」

スレッド優先順位の取得 

「thr_getprio(3T)」

スレッドの生成

thr_create(3T) は、Solaris スレッドライブラリルーチンの中で最も精巧なルーチンの 1 つです。

thr_create(3T)

thr_create(3T) は、現在のプロセスに新しい制御スレッドを追加します。(POSIX スレッドについては、「pthread_create(3T) 」を参照)。

新しいスレッドは保留状態のシグナルは継承しませんが、優先順位とシグナルマスクを継承することに注意してください。


#include <thread.h>

int thr_create(void *stack_base, size_t stack_size,
    void *(*start_routine) (void *), void *arg, long flags,
    thread_t *new_thread);

size_t thr_min_stack(void);

stack_base - 新しいスレッドが使用するスタックのアドレスを指定します。NULL を指定すると、新しいスレッドに stack_size バイト以上の大きさをもつスタックが割り当てられます。

stack_size - 新しいスレッドが使用するスタックのバイト数を指定します。0 を指定するとデフォルト値が使用されます。通常は 0 を指定してください。それ以外の値を指定する場合は、thr_min_stack() で戻された値よりも大きな値を指定してください。

通常は、スレッドのためのスタック空間を割り当てる必要はありません。スレッドライブラリが、各スレッドのスタック用に 1M バイトの仮想記憶をスワップ空間の予約なしで割り当てます。(スレッドライブラリは、mmap(2)MAP_NORESERVE オプションを使って割り当てます。)

start_routine - 新しいスレッドで実行する関数を指定します。start_routine() で指定した関数が終了すると、スレッドはその関数の戻り値を終了状態に設定して終了します (詳細は、「thr_exit(3T)」を参照) してください。

arg - void で記述される任意のもの。通常は 4 バイト値です。それよりも大きな値は、そのポインタを引数とすることによって間接的に渡さなければなりません。

引数は 1 つしか指定できません。複数の引数を与えるためには、それらを 1 つのものとして (構造体に入れるなどの方法で) コーディングしてください。

flags - 生成されるスレッドの属性を指定します。通常は 0 を指定します。

flags の値は、以下に示すフラグのビット単位の論理和となります。


注 -

明示的な同期によって阻止されなければ、停止していない切り離されたスレッドは、そのスレッドの生成元が thr_create() から復帰する前に終了でき、そのスレッド識別子は別の新しいスレッドに割り当てることができます。


new_thread - NULL 以外を指定すると、new_thread の指すアドレスに新しいスレッドのスレッド識別子が格納されます。この引数が指す記憶領域は、呼び出し側の責任で確保しなければなりません。このスレッド識別子は、呼び出し側のプロセス内でだけ有効です。

スレッド識別子が特に必要でなければ、new_thread に 0 を指定してください。

戻り値

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


EAGAIN

システム制限を超えました。たとえば、生成された LWP が多すぎます。


ENOMEM

新しいスレッドを生成するための十分なメモリーがありません。


EINVAL

stack_base が NULL でなく、しかも stack_sizethr_min_stack() の戻り値より小さな値を指定しました。

スタックの動作

Solaris スレッドでのスタックの動作は、通常は pthread の場合と同じです。スタックの設定と操作の詳細は、「スタックについて」を参照してください。

thr_min_stack() を呼び出すと、スタックの大きさの絶対最小値が得られます。この関数は、NULL 手続きを実行するスレッドに必要なスタック空間の大きさを戻します。実用的なスレッドに必要なスタック空間はもっと大きいので、スタックの大きさを小さくするときは十分注意してください。

独自のスタックを指定する方法は 2 通りあります。1 つは、thr_create() でスタックアドレスを NULL に指定し、スタック空間の割り当てをスレッドライブラリに任せる方法です。スタックの大きさを指定するパラメタには、希望の大きさを指定します。

もう 1 つの方法は、thr_create() でスタックアドレスを指定して、スタックをすべて自分で管理する方法です。この場合は、スタック空間の割り当てだけでなく解放もユーザ自身で行う必要があります。つまり、スレッドの終了時にスタックを処分しなければなりません。

独自のスタックを割り当てる場合は、mprotect(2) を呼び出して、スタックの最後に必ずレッドゾーンを付加してください。

最小のスタックの大きさの取得

thr_min_stack(3T)

thr_min_stack(3T) は、スレッドの最小のスタックの大きさを取得します。


#include <thread.h>

size_t thr_min_stack(void);

NULL スレッドを実行するために必要なスタック空間の大きさが戻されます (NULL スレッドとは、中身のない (NULL) 手続きを実行するために生成されるスレッドのことです)。

スレッドが NULL 手続きでなく通常の手続きを実行する場合は、thr_min_stack() の戻り値よりも大きなスタックの大きさを割り当てなければなりません。

スレッドの生成時に、ユーザが独自のスタックを指定する場合は、そのスレッドを実行するために十分な大きさのスタック空間を、ユーザ自身が確保しなければなりません。動的にリンクされるような実行環境では、スレッドのスタックの大きさの最小限必要な量を見積もることは困難です。

通常、ユーザ独自のスタックが必要になることはまれです。実際、アプリケーション側が実行環境を完全に制御するなどのごく限られた状況でしか必要になりません。

ユーザは、スレッドライブラリにスタックの割り当てを任せることができます。スレッドライブラリのデフォルトのスタックは、すべてのスレッドの要求を満たします。

スレッド識別子の取得

thr_self(3T)

thr_self(3T) は、呼び出しスレッドの識別子を取得します。(POSIX スレッドについては、「pthread_self(3T)」を参照)。


#include <thread.h>

thread_t thr_self(void);

スレッドの実行明け渡し

thr_yield(3T)

thr_yield(3T) は、現在のスレッドから同じ優先順位か、より高い優先順位をもつ別のスレッドに実行権を譲ります。それ以外は何の効果もありません。thr_yield() の呼び出しスレッドがそうするという保証はありません。


#include <thread.h>

void thr_yield(void);

シグナルのスレッドへの送信

thr_kill(3T)

thr_kill(3T) は、スレッドにシグナルを送ります。(POSIX スレッドについては、「pthread_kill(3T)」を参照)。


#include <thread.h>
#include <signal.h>
int thr_kill(thread_t target_thread, int sig);

呼び出しスレッドのシグナルマスクのアクセス

thr_sigsetmask(3T)

thr_sigsetmask(3T) は、呼び出しスレッドのシグナルマスクの変更や照会を行います。


#include <thread.h>
#include <signal.h>
int thr_sigsetmask(int how, const sigset_t *set, sigset_t *oset);

スレッドの終了

thr_exit(3T)

thr_exit(3T) はスレッドを終了させます。(POSIX スレッドについては、「pthread_exit(3T)」を参照)。


#include <thread.h>

void thr_exit(void *status);

スレッドの終了待ち

thr_join(3T)

thr_join(3T) 関数はスレッドの終了を待ちます。(POSIX スレッドについては、「pthread_join(3T)」を参照)。


#include <thread.h>

int thr_join(thread_t tid, thread_t *departedid, void **status);

指定したスレッドの終了待ち


#include <thread.h>

thread_t tid;
thread_t departedid;
int ret;
int status;

/* スレッド「tid」の終了待ち、status の指定あり */
ret = thr_join(tid, &departedid, (void**)&status);

/* スレッド「tid」の終了待ち、status の指定なし */
ret = thr_join(tid, &departedid, NULL);

/* スレッド「tid」の終了待ち、departedid と status の指定なし */
ret = thr_join(tid, NULL, NULL); 

tid(thread_t) 0 の場合は、thr_join() はプロセス内の切り離されていない任意のスレッドの終了を待ちます。つまり、スレッド識別子を指定しなければ、切り離されていないスレッドのどれかが終了すると thr_join() が復帰します。

任意のスレッドの終了待ち


#include <thread.h>

thread_t tid;
thread_t departedid;
int ret;
int status;

/* スレッド「tid」の終了待ち、status の指定あり */
ret = thr_join(NULL, &departedid, (void **)&status); 

thr_join() でスレッド識別子として NULL を指定すると、プロセス内の切り離されていない任意のスレッドの終了を待ちます。departedid には、終了したスレッドのスレッド識別子が格納されます。

スレッド固有データ用キーの作成

関数名と引数を別にすれば、スレッド固有データは Solaris のものも POSIX のものも同じです。この節では、Solaris の関数の概要を説明します。

thr_keycreate(3T)

thr_keycreate(3T) は、プロセス内のスレッド固有データを識別するためのキーを割り当てます。(POSIX スレッドについては、「pthread_key_create(3T)」を参照)。


#include <thread.h>

int thr_keycreate(thread_key_t *keyp,
    void (*destructor) (void *value));

スレッド固有データ用キーの設定

thr_setspecific(3T)

thr_setspecific(3T) は、呼び出しスレッドで、値 (value) とスレッド固有データのキー (key) を結び付けます。(POSIX スレッドについては、「pthread_setspecific(3T)」を参照)。


#include <thread.h>

int thr_setspecific(thread_key_t key, void *value);

スレッド固有データ用キーの取得

thr_getspecific(3T)

thr_getspecific(3T) は、key で指定したキーに結び付けられている現在の値を、valuep が指している位置に格納します。(POSIX スレッドについては、「pthread_getspecific(3T)」を参照)。


#include <thread.h>

int thr_getspecific(thread_key_t key, void **valuep);

スレッド優先順位の設定

Solaris スレッドでは、優先順位が親と異なるスレッドを生成する場合、SUSPEND モードで生成します。そして、停止状態のときに thr_setprio(3T) 関数を使ってスレッド優先順位を変更し、実行を再開します。

通常、非結合スレッドのスケジューリングは、プロセス内の他のスレッドとの関係だけを考慮した単純な優先順位に基づいて行われます。その他の調整が行われたり、カーネルが関係したりすることはありません。スレッドの優先順位は通常は同一であり、生成側プロセスの優先順位を継承します。

thr_setprio(3T)

thr_setprio(3T) は、現在のプロセス内の tid で指定したスレッドの優先順位を、newprio で指定した優先順位に変更します。(POSIX スレッドについては、「pthread_setschedparam(3T)」を参照)。


#include <thread.h>

int thr_setprio(thread_t tid, int newprio)

スレッドのスケジューリングは、デフォルトの設定では、最低の優先順位を表す 0 から最大整数までの範囲の固定的な優先順位に基づいて行われます。tid で指定されたスレッドは、自分より優先順位の低いスレッドから実行リソースを横取りし、自分より優先順位の高いスレッドには実行リソースを譲ります。


thread_t tid;
int ret;
int newprio = 20;

/* 停止状態のスレッドを生成する */
ret = thr_create(NULL, NULL, func, arg, THR_SUSPEND, &tid);

/* 停止状態の子スレッドに対して新しい優先順位を設定する */
ret = thr_setprio(tid, newprio);

/* 停止状態の子スレッドを新しい優先順位で開始する */
ret = thr_continue(tid);

スレッド優先順位の取得

thr_getprio(3T)

thr_getprio(3T) は、スレッドの現在の優先順位を取得します。各スレッドは生成側の優先順位を継承します。thr_getprio() は、tid で指定されたスレッドの現在の優先順位を、newprio が指している位置に格納します。(POSIX スレッドについては、「pthread_getschedparam(3T)」を参照)。


#include <thread.h>

int thr_getprio(thread_t tid, int *newprio)

pthread に相当するものがある同期関数 − 相互排他ロック

mutex の初期化

mutex_init(3T)


#include <synch.h> (または #include <thread.h>)

int mutex_init(mutex_t *mp, int type, void *arg)); 

mutex_init(3T) は、mp が指す相互排他ロック (mutex ロック) を初期化します。type には、次のいずれかを指定できます (arg は現在は無視されます)。(POSIX スレッドについては、「mutex の初期化」を参照)。

USYNC_PROCESS ロックした状態でプロセスが終了すると、次にそのロックを要求したスレッドは滞ります。これは、クライアントプロセスとロックを共有するシステムで起こる問題で、クライアントプロセスが強制的に終了されることがあり得るからです。ロックしたままプロセスが終了する問題を回避するには、USYNC_PROCESS_ROBUST で mutex をロックします。USYNC_PROCESS_ROBUST には次の 2 つの機能があります。

0 に初期化されたメモリーに領域を確保することによって mutex を初期化することもできます。その場合は typeUSYNC_THREAD を指定したものと仮定されます。

複数のスレッドから同じ mutex を同時に初期化してはいけません。一度初期化した mutex は、他のスレッドが使用している可能性があるので再初期化してはいけません。

プロセス内スコープでの mutex


#include <thread.h>

mutex_t mp;
int ret;

/* このプロセスの中だけで使用する */
ret = mutex_init(&mp, USYNC_THREAD, 0); 

プロセス間スコープでの mutex


#include <thread.h>

mutex_t mp;
int ret;

/* すべてのプロセスの間で使用する */
ret = mutex_init(&mp, USYNC_PROCESS, 0); 

プロセス間スコープの確実な mutex


#include <thread.h>

mutex_t mp;
int ret;

/* to be used among all processes */
ret = mutex_init(&mp, USYNC_PROCESS_ROBUST, 0); 

mutex の削除

mutex_destroy(3T)


#include <thread.h>

int mutex_destroy (mutex_t *mp);

mutex_destroy(3T) は、mp が指す mutex を削除します。mutex を格納する領域は解放されません。(POSIX スレッドについては、「pthread_mutex_destroy(3T)」を参照)

mutex の獲得

mutex_lock(3T)


#include <thread.h>

int mutex_lock(mutex_t *mp);

mutex_lock(3T) は、mp が指す mutex をロックします。mutex がすでにロックされている場合は、使用可能になるまで呼び出しスレッドがブロックされます (ブロック状態のスレッドは、優先順位別の待ち行列に入れられます)。(POSIX スレッドについては、「pthread_mutex_lock(3T)」を参照)。

mutex の解除

mutex_unlock(3T)


#include <thread.h>

int mutex_unlock(mutex_t *mp);

mutex_unlock(3T) は、mp が指す mutex のロックを解除します。mutex はロックされていなければならず、しかも呼び出しスレッドがその mutex を最後にロックした (つまり、現在保持している) スレッドでなければなりません。(POSIX スレッドについては、「pthread_mutex_unlock(3T)」を参照)。

mutex の獲得 (ブロックなし)

mutex_trylock(3T)


#include <thread.h>

int mutex_trylock(mutex_t *mp);

mutex_trylock(3T) は、mp が指す mutex をロックしようとします。この関数はブロックしない点を除いて、mutex_lock() と同じ働きをします。(POSIX スレッドについては、「pthread_mutex_trylock(3T)」を参照)。

pthread に相当するものがある同期関数 − 条件変数

条件変数の初期化

cond_init(3T)


#include <thread.h>

int cond_init(cond_t *cv, int type, int arg);

cond_init は、cv が指す条件変数を初期化します。type には、次のいずれかを指定できます (arg は現在は無視されます)。(POSIX スレッドについては、「pthread_condattr_init(3T)」を参照)。

0 に初期化されたメモリーに領域を確保することによって、条件変数を初期化することもできます。その場合は、typeUSYNC_THREAD を指定したものと仮定されます。

複数のスレッドから、同じ条件変数を同時に初期化してはいけません。一度初期化した条件変数は他のスレッドが使用している可能性があるので、再初期化してはいけません。

プロセス内スコープでの条件変数


#include <thread.h>

cond_t cv;
int ret;

/* このプロセスの中だけで使用する */
ret = cond_init(cv, USYNC_THREAD, 0); 

プロセス間スコープでの条件変数


#include <thread.h>

cond_t cv;
int ret;

/* すべてのプロセスの間で使用する */
ret = cond_init(&cv, USYNC_PROCESS, 0); 

条件変数の削除

cond_destroy(3T)


#include <thread.h>

int cond_destroy(cond_t *cv);

cond_destroy(3T) は、cv が指す条件変数を削除します。条件変数を格納する領域は解放されません。(POSIX スレッドについては、「pthread_condattr_destroy(3T)」を参照)。

条件変数によるブロック

cond_wait(3T)


#include <thread.h>

int cond_wait(cond_t *cv, mutex_t *mp);

cond_wait(3T) は、mp が指す mutex を原子操作により解放し、cv が指す条件変数で、呼び出しスレッドをブロックします。ブロックされたスレッドを呼び起こすには、cond_signal()cond_broadcast() を使います。また、スレッドはシグナルや fork() の割り込みによっても呼び起こされます。(POSIX スレッドについては、「pthread_cond_wait(3T)」を参照)。

条件変数による指定時刻付きブロック

cond_timedwait(3T)


#include <thread.h>

int cond_timedwait(cond_t *cv, mutex_t *mp, timestruct_t abstime)

cond_timedwait(3T) は、abstime で指定した時刻を過ぎるとブロック状態を解除する点を除いて、cond_wait() と同じ動作をします。(POSIX スレッドについては、「pthread_cond_timedwait(3T)」を参照)。

cond_timedwait() が戻るときは、たとえエラーを戻したときでも、常に mutex は呼び出しスレッドがロックし保持している状態にあります。

cond_timedwait() のブロック状態が解除されるのは、条件変数にシグナルが送られてきたときか、一番最後の引数で指定した時刻を過ぎたときです。時間切れの指定は時刻で行うため、時間切れの時刻を再計算する必要がないので、効率的に条件を再評価できます。

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

cond_signal(3T)


#include <thread.h>

int cond_signal(cond_t *cv);

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

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

cond_broadcast(3T)


#include <thread.h>

int cond_broadcast(cond_t *cv);

cond_broadcast(3T) は、cv が指す条件変数でブロックされている全スレッドのブロックを解除します。スレッドがブロックされていない条件変数に対して cond_broadcast() を実行しても無視されます。

pthread に相当するものがある同期関数 − セマフォ

セマフォの操作は Solaris オペレーティング環境と POSIX 環境の両方で同じです。関数名は、Solaris オペレーティング環境で sema_ だった関数名が pthread では sem_ に変わっています。

セマフォの初期化

sema_init(3T)


#include <thread.h>

int sema_init(sema_t *sp, unsigned int count, int type,
    void *arg);

sema_init(3T) は、sp が指すセマフォ変数に count の値を初期設定します。type には、次のいずれかを指定できます (arg は現在は無視されます)。

USYNC_PROCESS: 現在のプロセス内のスレッドと他のプロセス内のスレッドとの間で同期をとることができるようにします。ただし、セマフォを初期化するプロセスは 1 つだけに制限してください。arg は無視されます。

USYNC_THREAD: 現在のプロセス内のスレッドの間でだけ同期をとることができるようにします。arg は無視されます。

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

プロセス内スコープでのセマフォ


#include <thread.h>

sema_t sp;
int ret;
int count;
count = 4;

/* このプロセスの中だけで使用する */
ret = sema_init(&sp, count, USYNC_THREAD, 0); 

プロセス間スコープでのセマフォ


#include <thread.h>

sema_t sp;
int ret;
int count;
count = 4;

/* すべてのプロセスの間で使用する */
ret = sema_init (&sp, count, USYNC_PROCESS, 0); 

セマフォの加算

sema_post(3T)


#include <thread.h>

int sema_post(sema_t *sp);

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

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

sema_wait(3T)


#include <thread.h>

int sema_wait(sema_t *sp);

sema_wait(3T) は、sp が指すセマフォの値が、0 より大きくなるまでスレッドをブロックし、0 より大きくなったらセマフォの値を原子操作によって 1 減らします。

セマフォの減算

sema_trywait(3T)


#include <thread.h>

int sema_trywait(sema_t *sp);

sema_trywait(3R) は、sp が指すセマフォの値が 0 より大きい場合、原子操作によって 1 減らします。この関数はブロックしない点を除いて、sema_wait() と同じ働きをします。

セマフォの削除

sema_destroy(3T)


#include <thread.h>

int sema_destroy(sema_t *sp);

sema_destroy(3R) は、sp が指すセマフォを削除します。セマフォを格納する領域は解放されません。

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

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

以上により、その同期変数に対する操作は、typeUSYNC_THREAD のときとまったく同じように実行されます。


mutex_init(&m, USYNC_PROCESS, 0);
rwlock_init(&rw, USYNC_PROCESS, 0);
cond_init(&cv, USYNC_PROCESS, 0);
sema_init(&s, count, USYNC_PROCESS, 0);

プロセス間での LWP の使用

プロセス間でロックと条件変数を使用する場合、必ずしもスレッドライブラリを使用しなければならないわけではありません。基本的にはスレッドライブラリを使用するものの、それが望ましくないときは、_lwp_mutex_* インタフェースと _lwp_cond_* インタフェースを次のようなやり方で使用するというアプローチを使用できます。

  1. ロックと条件変数を通常どおり (shmop(2) または mmap(2) を使用して) 共有メモリーに確保します。

  2. 新たに割り当てられたオブジェクトを USYNC_PROCESS タイプとして初期化します。この初期化のために使用できるインタフェースはないので (_lwp_mutex_init(2)_lwp_cond_init(2) は存在しない)、それらのオブジェクトは静的に割り当てて初期化したダミーオブジェクトを使って初期化します。

たとえば、lockp を初期化するには次のようにします。


	lwp_mutex_t *lwp_lockp;
	lwp_mutex_t dummy_shared_mutex = SHAREDMUTEX;
		/* SHAREDMUTEX は /usr/include/synch.h の中で定義されている */
	...
	...
	lwp_lockp = alloc_shared_lock();
	*lwp_lockp = dummy_shared_mutex;

同様に、条件変数については次のようにします。


	lwp_cond_t *lwp_condp;
	lwp_cond_t dummy_shared_cv = SHAREDCV;
		/* SHAREDCV は /usr/include/synch.h の中で定義されている */
	...
	...
	lwp_condp = alloc_shared_cv();
	*lwp_condp = dummy_shared_cv;

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

例 9-2 では、「生産者 / 消費者」問題の生産者と消費者をそれぞれ別のプロセスで表現しています。メインルーチンは、0 に初期化されたメモリーを自分のアドレス空間にマッピングし、それを子プロセスと共有します。mutex_init()cond_init() を呼び出さなければならないのは、それらの同期変数のタイプが USYNC_PROCESS だからです。

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

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

例 9-2 のデータ構造は、条件変数による「生産者 / 消費者」のコーディング例のデータ構造と同じです (詳細は、「片方向リンクリストの入れ子のロック」を参照してください)。


例 9-2 「生産者 / 消費者」問題 − USYNC_PROCESS を使った例


main() {
    int zfd;
    buffer_t *buffer;

    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;

    mutex_init(&buffer->lock, USYNC_PROCESS, 0);
    cond_init(&buffer->less, USYNC_PROCESS, 0);
    cond_init(&buffer->more, USYNC_PROCESS, 0);
    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);
    }
}

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

fork() と Solaris スレッドに関する問題

Solaris スレッドと POSIX スレッドでは、fork() の動作に関する定義が異なります。fork() の問題の詳細は、「プロセスの作成 − exec(2) と exit(2) について」を参照してください。

Solaris libthread は、fork()fork1() の両方をサポートします。fork() 呼び出しは「汎用 fork」セマンティクスをもち、スレッドと LWP を含むプロセス内のすべてを複製します。つまり、親の完全なクローンを作成します。一方、fork1() 呼び出しで作成されるクローンはスレッドを 1 つしかもちません。プロセスの状態とアドレス空間は複製されますが、スレッドについては呼び出しスレッドが複製されるだけです。

POSIX libpthread は 、fork() のみをサポートします。そのセマンティクスは、Solaris スレッドにおける fork1() と同じです。

fork() のセマンティスクが「汎用 fork」と「fork1」のどちらになるかは、どちらのライブラリを使用するかで決まります。-lthread を使ってリンクすれば「汎用 fork」セマンティクス、-lpthread を使ってリンクすれば「fork1」セマンティクスになります。

詳細は、libthread または libpthread とのリンク」を参照してください。