Go to the first, previous, next, last section, table of contents.


ライブラリの概要

この章では, スレッドを用いたプログラミングの概要について解説します.

スレッドの操作の概要

スレッドの生成

新たなスレッドを生成するためには, pthread_create() を用います. この関数は, 指定したアトリビュート (See section スレッドアトリビュートオブジェクト.) を用いてスレッドオブジェクト (ライブラリ内部で使用するオブジェクトで, スレッドを管理するために用いる) を生成し, 指定した関数 (スレッド開始関 数) からスレッドの実行を開始します.

スレッドの終了

スレッドの実行は, 以下の場合に終了します.

スレッドが終了する際, 以下の動作が行なわれます.

  1. pthread_cleanup_push() 等によってプッシュされ, まだ pthread_cleanup_pop() 等によってポップされていない Cleanup ハン ドラが, プッシュした逆の順序で実行されます. (See section スレッド Cleanup.)
  2. pthread_join() を用いてそのスレッドの終了を待っている他の スレッドに, スレッドの返り値 (スレッド開始関数の返り値, あるいは pthread_exit() の引数) が渡され, それらのスレッドがアンブロック されます.
  3. Thread-Specific データのデストラクタ関数が, 定義されない順序で呼ばれ, そのスレッドの Thread-Specific データを全て破棄します. (See section Thread-Specific データの概要.)
  4. スレッドが既にデタッチされていれば, スレッドオブジェクトを回収しま す. (See section スレッドの削除.)

スレッドが終了したあと, 他にスレッドが存在しなければ, プロセスは exit し ます. このときの終了ステータスは, pthread_set_exit_status_np() に よって設定された値です. もし, 設定されていなかった場合, 最後のスレッドが デタッチされていれば 0, そうでなければ 1 となります.

スレッドの終了の Wait

他のスレッドの終了を待つには, pthread_join() を用います. この関数 を呼び出したスレッドは, 指定したスレッドが終了するまでブロックします. 複数のスレッドが同一のスレッドに対して pthread_join() を呼び出し た場合, 指定したスレッドが終了すると, 全てのスレッドがアンブロックされま す.

デタッチされているスレッドに対して pthread_join() を呼んではい けません.

スレッドの削除

スレッドは, 終了してもデタッチされるまでは完全には削除されません. デタッ チするためには, 以下のいずれかの操作が必要です.

スレッドが終了する前に pthread_detach() が呼ばれると, スレッド に「終了したら削除しても良い」というフラグを立てます. デタッチされた スレッドに対して pthread_join() してはいけません.

メモリを有効に利用するため, スレッドはいつかはデタッチされるべきです.

スレッドのサスペンドの概要

スレッドから, 他のスレッドをサスペンド(一時停止)させることができます. また, スレッドを生成する際に, サスペンドされた状態で生成することもでき ます.

スレッドをサスペンドするには, pthread_suspend_np() を用います. またサスペンドされたスレッドを再開するには pthread_resume_np() を用います.

スレッドをサスペンドされた状態で生成するためには, サスペンドステート属 性を 1 にしたスレッドアトリビュートオブジェクトを pthread_create() で指定します(See section サスペンドステート.) こうやって生成されたスレッドは, pthread_resume_np() が 呼ばれるまで実行されません.

パッケージの動的な初期化

プログラムには, 初期化ルーチンなど, 一度しか実行して欲しくない部分があ ります. このような個所は Mutex を用いて記述できますが (See section Mutex.), Mutex は初期化されないと使用できないため, 実際 にはこの方法は現実的ではありません. このため, ライブラリには, 関数やブ ロックを一度だけ実行するための機構があります.

pthread_once() は, 指定した関数を一度だけ実行させるために用います. 2度目以降の pthread_once() の呼び出し時に, 最初の pthread_once() の実行が終了していなかった場合は, 最初の pthread_once() の実行が終了するまでブロックします. 2度目以降の pthread_once() の呼び出しは, 最初の呼び出しが終了していた場合, 単 に無視されます.

pthread_first_np(), pthread_first_done_np() は, 関数内の特 定のブロック (初期化ブロック) を一度だけ実行するために用います. 関数内 static 変数の初期化等に有効です. pthread_first_np() は最初に実行 すると 1 を返し, 2度目以降の呼び出しには 0 を返します. また pthread_first_done_np() は, 初期化ブロックの実行が終了したことを 宣言します. 2度目以降の pthread_first_np() 呼び出し時に, まだ pthread_first_done_np() が実行されていなければ, 実行されるまでブ ロックされます.

foo()
{
    static pthread_once_t once = PTHREAD_ONCE_INIT;
    static int need_initialized;
    if (pthread_first_np(&once)) {
        need_initialized = appropriate_value();	/* Initialize */
            :
            :
        pthread_first_done_np(&once);
    }
    ....
}

スレッドに対するネーミング

スレッドにはそれぞれ名前をつけることができます. これは, デバッグの時に役 立ちます. スレッドを生成した時点では, 名前は付いていません.

スレッドに名前を付けるためには, ptheread_set_name_np() を, 名前を 得るためには, pthread_get_name_np() を用います.

初期スレッドについて

main() 関数から始まるコンテキストのことを, 初期スレッド と言います. 初期スレッドのスケジューリング属性は, 以下の通りです.

スケジューリングポリシー
SCHED_OTHER
スケジューリングプライオリティ
(PRI_OTHER_MIN+PRI_OTHER_MAX)/2

初期スレッドがリターンすると, プロセス全体が exit します. 初期スレッド を終了したいが, 他のスレッドは動かしておきたい場合, pthread_exit() を使用してください.

また, 初期スレッドの Interruptibility state については, See section Interruptibility States. を参照してください.

PTL を C 言語から使用する場合, <pthread.h> によって, ユーザの main 関数は, __pthread_main という名前に #define されてい ます. 本当の main 関数は libpthreads.a の中にあります.

アトリビュートオブジェクトの概要

アトリビュートオブジェクト は, スレッド, Mutex, Condition Variable のさまざまな属性を記述するために用いられます. アトリビュート オブジェクトは, スレッド, Mutex, Condition Variable のそれぞれに対応し てスレッドアトリビュートオブジェクト, Mutex アトリビュートオブジェクト, Condition アトリビュートオブジェクトがあります.

オブジェクト (スレッド, Mutex, Condition Variable) を生成する際には, 対応するアトリビュートオブジェクトを指定するか, デフォルトのアトリビュー トを指定します.

アトリビュートオブジェクトの生成

アトリビュートオブジェクトの生成は以下の関数で行ないます.

スレッドアトリビュート
pthread_attr_init()
Mutex アトリビュート
pthread_mutexattr_init()
Condition アトリビュート
pthread_condattr_init()

これらの関数は, デフォルトの属性を持つアトリビュートオブジェクトを生 成します. 属性を変更するためには, See section スレッドアトリビュートオブジェクト., See section Mutex アトリビュートオブジェクト., See section Condition アトリビュートオブジェクト. を参照して下さい.

オブジェクトを生成する際に, (必要な)アトリビュートオブジェクトの内容はコ ピーされます. つまり, オブジェクトを生成した後にアトリビュートオブジェク トの内容を変更しても, 既に生成されたオブジェクトには影響を与えません.

アトリビュートオブジェクトの削除

アトリビュートオブジェクトの削除は以下の関数で行ないます.

スレッドアトリビュート
pthread_attr_destroy()
Mutex アトリビュート
pthread_mutexattr_destroy()
Condition アトリビュート
pthread_condattr_destroy()

オブジェクトを生成した後にアトリビュートオブジェクトを削除しても, 既 に生成されたオブジェクトには影響を与えません.

スレッドアトリビュートオブジェクト

スレッドアトリビュートオブジェクト は, 新たに(これから)生成す るスレッドの属性を制御します. スレッドアトリビュートオブジェクトは以 下のようにして使用します.

  1. pthread_attr_init() を用いてスレッドアトリビュートオブ ジェクトを生成する.
  2. 以下に述べる関数を用いてスレッドアトリビュートオブジェクトの 各属性を変更する.
  3. 生成したスレッドアトリビュートオブジェクトを指定して pthread_create() を呼び, スレッドを生成する.

あるいは, pthread_create() に与えるスレッドアトリビュートとし て, NULL あるいは大域変数 pthread_attr_default を与えることに よってデフォルトスレッドアトリビュートを用いることも出来ます.

スレッドアトリビュートオブジェクトで指定できる属性には以下のものがあ ります.

PTL では, スレッドアトリビュートオブジェクトのデフォルトとして,それぞれ の属性に以下の値を与えます. 属性の値の意味に関しては後の章を参照してくだ さい.

属性
スケジューリングポリシー
SCHED_OTHER
スケジューリングプライオリティ
(PRI_OTHER_MIN+PRI_OTHER_MAX)/2
Inherit スケジューリング
PTHREAD_DEFAULT_SCHED
コンテンションスコープ
PTHREAD_SCOPE_LOCAL
スタックプロパティ
PTHREAD_STACK_SAFE_NP
スタックサイズ
16Kbytes
デタッチステート
0 (not detached)
サスペンドステート
0 (not suspended)

大域変数 (pthread_attr_t)pthread_attr_default はこのデフォル ト属性を保持するスレッドアトリビュートオブジェクトです. 以下の場合に このデフォルトが使用されます.

スケジューリングポリシー

スケジューリングポリシー は, スレッドがどのようにプロセスの中 でスケジュールされるかを指定します. スケジューリングポリシーには以下 の3種類があります.

SCHED_FIFO
最も高いプライオリティのスレッドが, ブロックするまで実行されます. 最 も高いプライオリティに複数のスレッドが存在した場合, 最初のスレッドが, ブロックするまで実行されます.
SCHED_RR
最も高いプライオリティのスレッドが, ブロックするまで実行されます. 最も高 いプライオリティに複数のスレッドが存在した場合, スレッドは途中でCPU を奪 われ, そのプライオリティの次のスレッドが実行権を得ます (タイムスライス).
SCHED_OTHER (デフォルト)
全てのスレッドはタイムスライスによって CPU を奪われます. 高いプライオリ ティのスレッドほど CPU を多く握れますが, 低いプライオリティのスレッドに も実行権は回ります. SCHED_FIFO, SCHED_RR の実行可能なスレッドが存在すれ ば, このポリシーのスレッドの実行は行なわれません.

以下の方法で, スケジューリングポリシーを変更することが出来ます.

スケジューリングに関しての詳細は, See section スケジューリングの概要. を参照して下さい.

スケジューリングプライオリティ

スケジューリングプライオリティ は, スレッドの実行の優先度を定 めます. スケジューリングプライオリティの値の範囲は, スケジューリング ポリシーによって異なります.

ポリシー     最小プライオリティ  最大プライオリティ
SCHED_FIFO     PRI_FIFO_MIN        PRI_FIFO_MAX
SCHED_RR       PRI_RR_MIN           PRI_RR_SCHED
SCHED_OTHER   PRI_OTHER_MIN       PRI_OTHER_MAX

あるいは, sched_get_priority_min(), sched_get_priority_max() 関数を用いることも出来ます.

Scheduling Policy Attributeに関しての詳細は, See section スケジューリングの概要. を参照してください.

Inherit スケジューリング

Inherit スケジューリング 属性は, pthread_create() で新 たに生成されたスレッドが, 作成するスレッドのスケジューリング属性を継 承するか, あるいはスレッドアトリビュートオブジェクトので指定したスケ ジューリング属性によって設定されるかを指定します.

@tindex{PTHREAD_DEFAULT_SCHED} @tindex{PTHREAD_INHERIT_SCHED} Inherit スケジューリング属性の値は, PTHREAD_DEFAULT_SCHED (デフォ ルト) か, PTHREAD_INHERIT_SCHED です. pthread_create() で 指定したスレッドアトリビュートの Inherit スケジューリング属性が, PTHREAD_INHERIT_SCHED だった場合, 生成されるスレッドのスケジューリング属 性 (スケジューリングポリシー, プライオリティ) は生成するスレッド (すなわ ち, pthread_create() を呼び出したスレッド) の値を引き継ぎます.

Inherit スケジューリング属性を変更するためには, pthread_attr_setinheritsched() 関数を使用します.

コンテンションスコープ

@tindex{PTHREAD_SCOPE_GLOBAL} @tindex{PTHREAD_SCOPE_LOCAL}

コンテンションスコープ 属性は, PTHREAD_SCOPE_GLOBALPTHREAD_SCOPE_LOCAL のいずれかで, スレッドのコンテンションスコー プを定義します. コンテンションスコープとは, スレッドのスケジューリングの 際に, プロセス内のスレッドだけを考慮する (PTHREAD_SCOPE_LOCAL) か, あるいは, 他のプロセス内のスレッドも考慮する (PTHREAD_SCOPE_GLOBAL)かを指定するもの(らしい)です.

PTL では, コンテンションスコープは PTHREAD_SCOPE_LOCAL のみサポー トしています. PTHREAD_SCOPE_GLOBAL に設定することは無意味で す.

コンテンションスコープを設定するためには, pthread_attr_setscope() 関数を使用します.

スタックプロパティ

マルチスレッド環境では, スタックはスレッド毎に必要です. 通常の UNIX 上のプロセスではスタックが溢れた場合, カーネルが自動的にスタックセグ メントを拡張します. しかし, スレッドスタックは, カーネルが管理してい ないため, このような処理はライブラリ側で行なっています.

PTL では, 現在のところ3種類のスレッドスタック (共有メモリスタック, Redzone Protect スタック, ヒープメモリスタック) を提供しています. それ ぞれのスタックの特徴は以下の通りです.

共有メモリスタック
共有メモリスタックは OS が共有メモリ機構をサポートしている場合に使用 可能で, スタック溢れを検出できます. スタックが溢れた場合, 自動的に拡 張できる場合があります(アーキテクチャによる). スタックの確保に若干時 間がかかるという欠点があります.
Redzone Protect スタック
Redzone Protect スタックは OS が mprotect システムコールをサポートし ている場合に使用可能で, スタック溢れを検出できます(拡張は出来ません). スタックの確保に若干時間がかかるという欠点があります.
ヒープメモリスタック
ヒープメモリスタックは, どのアーキテクチャでも使用可能で, malloc() を用いてスタックを確保します. スタック溢れを検出でき ませんが, 三つの中で一番速く確保することが出来ます.

低レベルの実装を隠蔽するため, スタックを確保する際に, ユーザはこれらの スタックの種類を陽に指定することは出来ないようになっています. その代わ り, ユーザは pthread_attr_setstackprop_np() を用いて使用したい スタックの性質(スタックプロパティ)を伝えることが出来ます. 現在のところ プロパティとして以下の3つが定義されています. 複数のプロパティを論理和 で結合して pthread_attr_setstackprop_np() に渡すことが出来ます.

@tindex{PTHREAD_STACK_SAFE_NP} @tindex{PTHREAD_STACK_EXTENSIBLE_NP} @tindex{PTHREAD_STACK_NONE_NP}

PTHREAD_STACK_SAFE_NP
安全なスタック(溢れ検出可能)
PTHREAD_STACK_EXTENSIBLE_NP
自動拡張可能なスタック
PTHREAD_STACK_NONE_NP
特に性質を指定しない

デフォルトでは, PTHREAD_STACK_SAFE_NP が使用されます. ライブラリは, な るべく指定したプロパティを満たすスタックを確保しようと試みますが, 失敗 した場合は他の種類のスタックが割り当てられます.

これらのスタックは, スレッドが終了した後, スタックキャッシュに蓄えられ, 以後のスレッドの生成時に再利用されます.

また, pthread_alloc_stack_cache_np() を用いて指定した数のスタッ クを予めスタックキャッシュに用意しておくことができます. これによって, 一度に多数のスレッドを生成する際の時間を稼ぐことが出来ます.

pthread_alloc_stack_cache_np は PTL 独自の関数です. POSIX ではス タックの確保方法の指定について特に定めていません.

スタックサイズ

スタックサイズ 属性は, スレッドのスタックサイズを指定します. デフォ ルトでは 16K バイトです. 変更するためには pthread_attr_setstacksize() 関数を用います.

デタッチステート

デタッチステート 属性はスレッドが開始した際にデタッチされてい るかどうかの指定を行ないます.(See section スレッドの削除.) デフォルトではデタッチされていません. デタッチステートを変 更するためには, pthread_attr_setdetachstate() 関数を用います.

サスペンドステート

サスペンドステート 属性は, スレッドが開始した際にサスペンドされ ているかどうかの指定を行ないます. (See section スレッドのサスペンドの概要.) デフォルトではスレッドは開始時はサスペン ドされません. サスペンドステートを変更するためには, pthread_attr_setsuspended_np() 関数を用います.

Mutex アトリビュートオブジェクト

Mutex アトリビュートオブジェクト は, pthread_mutex_init() で生成する Mutex のアトリビュートを指定するために用います.

Mutex アトリビュートオブジェクトで指定できる属性には以下のものがあり ます.

PTL では, Mutex アトリビュートオブジェクトのデフォルトとして, それぞれの 属性に以下の値を与えます. 属性の値の意味に関しては後の章を参照してくださ い.

属性
プロセスシェアード属性
0 (not shared)
Mutex プロトコル属性
NO_PRIO_INHERIT
シーリング属性
PRI_FIFO_MAX

大域変数 (pthread_mutexattr_t)pthread_mutexattr_default はこ のデフォルト属性を保持する Mutex アトリビュートオブジェクトです. 以 下の場合にこのデフォルトが使用されます.

プロセスシェアード属性

プロセスシェアード属性 は, Mutex がプロセス間で共有されるかどうか を指定します. PTL ではこの属性はサポートしていません. 全てのMutex はプロ セスローカルです.

プロセスシェアード属性を変更するためには, pthread_mutexattr_setpshared() 関数を用います.

Mutex プロトコル属性

プライオリティの逆転(See section Mutex.)を避けるため, Mutex をロックするスレッドのプライオリティを動的に変更することが出来ます. どのような方法で, プライオリティを変更するかを, Mutex プロトコル 属性によって指定します. Mutex プロトコルには以下の3種類があります.

Mutex プロトコル属性を変更するためには, pthread_mutexattr_setprotocol() 関数を用います.

詳しくは See section プライオリティの逆転の回避. を参照してください.

シーリング属性

シーリング属性 は, Mutex プロトコル属性(See section Mutex プロトコル属性.) がPRIO_PROTECT の場合に意味を持ち ます. (PTL では, まだPRIO_PROTECT プロトコルを実装していません)

シーリング属性を変更するためには, pthread_mutexattr_setprio_ceiling() 関数を用います.

シーリングについては, See section プライオリティの逆転の回避. を参照してください.

Condition アトリビュートオブジェクト

Condition アトリビュートオブジェクト は, pthread_cond_init() で生成する Condition Variable のアトリビュー トを指定するために用います.

Condition アトリビュートオブジェクトで指定できる属性には以下のものがあり ます.

PTL では, Condition アトリビュートオブジェクトのデフォルトとして, それぞ れの属性に以下の値を与えます. 属性の値の意味に関しては後の章を参照してく ださい.

属性
プロセスシェアード属性
0 (not shared)

大域変数 (pthread_condattr_t)pthread_condattr_default はこ のデフォルト属性を保持する Condition アトリビュートオブジェクトです. 以 下の場合にこのデフォルトが使用されます.

Condition Variable プロセスシェアード属性

プロセスシェアード 属性は, Condition Variable がプロセス間で共有さ れるかどうかを指定します. PTL ではこの属性はサポートしていません. 全ての Condition Variable はプロセスローカルです.

プロセスシェアード属性を変更するためには, pthread_condattr_setpshared() 関数を用います.

同期機構の概要

マルチスレッドプログラムでは, しばしばスレッド間で同期を取る必要があ ります. 例えば, スレッド間で共有している大域データにアクセスする場合 や, 他のスレッドで行なわれている作業が完了するまで待つような場合です. Pthreads では, 同期のためのオブジェクトとして, Mutex と Condition Variable を提供しています.

Mutex

Mutex は, MUTual EXclusion の略で, 複数のスレッドが同時にスレッ ド間で共有しているリソース (大域データ等) にアクセスできないようにするた めのものです. Mutex には二つの状態 -- ロックされている状態と, アンロッ クされている状態 -- があります. スレッドから共有リソースにアクセスする ためには以下のような手順を踏みます.

  1. 共有リソースと結び付けられた Mutex をロック.
  2. 共有リソースにアクセス.
  3. ロックした Mutex をアンロック.

1. で Mutex をロックする際に, その Mutex が既に他のスレッドによってロッ クされていた場合には, 後からロックしようとしたスレッドの実行は Mutex がアンロックされるまで実行を停止します.

Mutex を用いて共有リソースを保護するのはプログラマの責任です.

Mutex は排他的にアクセスしたい共有リソース毎に Mutex を生成しなければ なりません.

Mutex は, 「短い時間の」アクセスに限って使用されるべきです. 特に, Mutex をロックしている間にそのスレッドがブロックする可能性があるような 場合, Mutex を使用すべきではありません. このような場合は, Condition Variable を使用します. See section Condition Variable.

スレッドが終了する場合 (See section スレッドの終了.), ロックしている Mutexは自動的には 開放されません. PTL では, 終了した際にロックしている Mutex があると警告を発します. (See section キャンセルの概要.)

低いプライオリティのスレッドが Mutex をロックしている間に, 他の高いプラ イオリティのスレッドが同一の Mutex をロックしようとすると, 高いプライオ リティのスレッドが長期間ブロックされ得ます (プライオリティの逆転). これを避けるための機構が, Mutex には備わっています. 以下を参照してくださ い.

プライオリティの逆転の回避

プライオリティの逆転を避けるため, Mutex には以下の3種類の「プロトコル」 が定義されています.

NO_PRIO_INHERIT
@tindex{NO_PRIO_INHERIT} スレッドが NO_PRIO_INHERIT プロトコルを持つ Mutex をロックする際は, スレッ ドのプライオリティは Mutex の所有によって影響されることはありません(デフォ ルト). このプロトコルは, プライオリティの逆転を回避できません.
PRIO_INHERIT
@tindex{PRIO_INHERIT} スレッドが PRIO_INHERIT プロトコルを持つ Mutex をロックした後, より高い プライオリティのスレッドが同一の Mutex をロックしようとしてブロックした 場合, Mutex をロックしているスレッドのプライオリティは, その Mutex を待っ てブロックしている全てのスレッドの中で, 最も高いプライオリティのスレッド のプライオリティまで一時的に高められます. これによって, Mutex をロックし ているスレッドが高いプライオリティで実行されることになります. スレッドが Mutex をアンロックすると, プライオリティは元に戻ります.
PRIO_PROTECT
@tindex{PRIO_PROTECT} スレッドが PRIO_PROTECT プロトコルを持つ Mutex をロックすると, 直ちにそ のスレッドのプライオリティは, その Mutex の シーリング の値まで高 められます. スレッドが複数の PRIO_PROTECT プロトコルを持つ Mutex をロッ クしている場合は, それらのうちの最も高いシーリングをプライオリティとしま す. スレッドが Mutex をアンロックすると, プライオリティは元に戻ります. プライオリティの逆転を防ぐためには, Mutex のシーリングは, Mutex をロック する可能性の有る全てのスレッドの最も高いプライオリティと等しいか, それ以 上に設定されなければなりません. シーリングは SCHED_FIFO スケジューリングポリシーで許されるプライオリティ の範囲の値を取ります.

もし, スレッドが異なったプロトコルを持った複数の Mutex を同時に所有する 場合, スレッドはそれらのプロトコルによって得られる最高のプライオリティで 実行されます.

PTL では現在のところ PRIO_PROTECT は実装していません.

Mutex の生成と破棄

Mutex を生成するためには pthread_mutex_init() 関数を用います.

破棄するためには pthread_mutex_destroy() 関数を用います.

Mutex のロック

Mutex をロックするためには, pthread_mutex_lock() を用います. こ の関数は, Mutex がロックされていなければロックします. Mutex がロックさ れていれば, Mutex がアンロックされるまでブロックします. いずれにせよ, pthread_mutex_lock() からリターンしてきた際には Mutex はロック されています.

Mutex がロックされている場合にブロックして欲しくない場合のため, pthread_mutex_trylock() があります. これは, Mutex がロックされて いた場合, errno = EBUSY として -1 を返します.

Mutex をロックしているスレッドが更に同じスレッドをロックしようとすると, pthread_mutex_lock(), pthread_mutex_unlock() は errno = EDEADLK として -1 を返します.

Mutex のアンロック

Mutex をアンロックするためには pthread_mutex_unlock() 関数を用 います.

Mutex をアンロックし忘れると, 簡単にデッドロックを引き起こすので注意し てください. 特にキャンセル可能なスレッドでは, このことは重要です. 詳し くは See section キャンセルの概要. を参照してください.

Mutex に対するネーミング

Mutex にはそれぞれ名前をつけることができます. これは, デバッグの時に役 立ちます. Mutex を生成した時点では, 名前は付いていません.

Mutex に名前を付けるためには, ptheread_mutex_set_name_np() を, 名前を得るためには, pthread_mutex_get_name_np() を用います.

Condition Variable

Condition Variable は, 共有リソースが, 「特定の状態」になること を待つための仕組みです. Condition Variable は以下のように働きます.

スレッドは, 共有リソースが「自分の望む状態」になっているかどうかをチェッ クし, なっていなければ, ブロックします. 共有リソースの状態を変更したス レッドは, 状態を変更したことを他のスレッドに知らせます. 変更を通知され た(ブロックしている)スレッドはアンブロックされ, またリソースが「自分の 望む状態」かどうかを調べます.

例えば, 一つのスレッド (writer thread) が共有バッファに対して「要求」 を書き込み, もう一つのスレッド (reader thread) が共有バッファから「要 求」を読み取って処理を行なうような場合を考えます. バッファには数個の 「要求」を格納することができるとします. この場合, writer thread は共有 バッファを満たしてしまった場合, バッファに余裕ができるまで 「バッファ に空きがある」という Condition Variable でブロックします. 一方, reader thread はバッファから「要求」を読み取った後, バッファに空きが生じたの で, 「バッファに空きがある」という Condition Variable を「シグナル」し ます.

スレッドが共有リソース上のテスト(「自分の望む状態」かどうか?)を行なうコー ドは, 共有リソースへのアクセスを行なわなければならないため, 共有リソース と結び付いた Mutex で保護されなければなりません. (See section Mutex.). 実際, Condition Variable は, Mutex と必ず結び付いて使用されます.

Condition Variable でブロックする際には, 他のスレッドが共有リソースを アクセスできるように Mutex はアンロックされ, また, Condition Variable からアンブロックする際に Mutex が自動的に再びロックされます. Condition Variable でのブロックと, Mutex のアンロックは不可分に実行されます.

典型的な疑似コードを以下に示します. スレッド A は, 共有リソース上の特 定の<条件>が満たされた場合に処理 X を行ないます. スレッド B は共有リソー スにアクセスするもう一つのスレッドで, 処理 Y を行ないます.

スレッドAのコード

/* 条件をテストするため, Mutex をロック */
pthread_mutex_lock( 共有リソース_mutex );
while( <条件>が満たされていない ) {
        /*
         * <条件>が変更されるまで, Condition Variable でブロック.
         *  Mutex は自動的にアンロックされる.
         */
        pthread_cond_wait( 条件_cond , 共有リソース_mutex );
}
共有リソースに対する処理 X;
pthread_mutex_unlock( 共有リソース_mutex );

スレッドBのコード

pthread_mutex_lock( 共有リソース_mutex );
共有リソースに対する処理 Y;
/*
 * 変更を通知する.
 */
pthread_cond_signal( 条件_cond );
pthread_mutex_unlock( 共有リソース_mutex );

スレッド A は, 共有リソースにアクセスするために Mutex をロックし, <条 件>をテストします. もし<条件>が満たされていなければ, Condition Variable で Wait します. その際に, Mutex は自動的にアンロックされます. 一方スレッド B では, 共有リソースにアクセスするために Mutex をロックし, 処理 Y を行ないます. その後, スレッド A が共有リソース上の Condition Variable で Wait している場合に備え, pthread_cond_signal() によっ て Condition Variable を「シグナル」します. これによって, スレッド A は pthread_cond_wait() の Wait からアンブロックされ, Mutex を再 びロックして, <条件>をテストすることができます. スレッド B が pthread_cond_signal() を呼びだす際に, 他のスレッドが Condition Variable で Wait していなかった場合, 何も発生しません.

Condition Variable の生成と破棄

Condition Variable を生成するためには, pthread_cond_init() を用 います.

破棄するためには pthread_cond_destroy() を用います.

Condition Variable での Wait

Condition Variable で Wait するためには, pthread_cond_wait() を用 います. この関数は, 自動的に指定された Mutex をアンロックし, Condition Variable が「シグナル」されるまでブロックします. (See section Condition Variable のシグナル.) Mutex は必ずロックされていなければなりません. ロッ クされていない場合の動作は未定義です.

複数のスレッドから同一の Condition Variable で Wait を行なう際, pthread_cond_wait() には, 同一の Mutex が指定されなければなりま せん.

pthread_cond_wait() は, Condition Variable が「シグナル」される までブロックします. (See section Condition Variable のシグナル.)

指定した時刻が過ぎたら, Condition Variable での Wait から強制的に抜け 出したい場合は, pthread_cond_timedwait() を用います. ここで指定 する時刻は, 相対時刻 (何秒後) ではなく, 絶対時刻です. pthread_cond_timedwait() は, 時刻が過ぎると, errno = EAGAIN で, -1 を返します.

Condition Variable で Wait 中のスレッドにシグナルが配送された場合, シ グナルハンドラを呼び出す前に Mutex がロックされます. ロックされた Mutex は, シグナルハンドラからのリターンの後に自動的にアンロックされま す(シグナルハンドラから大域ジャンプを行なった場合を除く).

さらに Pthreads 規格では, 複数のスレッドが Condition Variable でブロッ クしていた場合, シグナルが配送されたスレッドには pthread_cond_signal() による Condition Variable の「シグナル」 を消費しないことになっていますが, 未実装です.

Condition Variable のシグナル

Condition Variable で Wait しているスレッドをアンブロックさせるために は, 2種類の方法があります.

pthread_cond_signal() では, アンブロックされるスレッドは, Condition Variable でブロックしているスレッドのうち, 最もプライオ リティの高いスレッドです. (See section スケジューリングの概要.)

pthread_cond_broadcast() では, Condition Variable を待っている 全てのスレッドがアンブロックされますが, 当然 Mutex をロックできるスレッ ドは(同時には)一つだけです. 他のスレッドは Condition Variable の Wait からは開放されますが, Mutex を待ってブロックすることになります.

その他の同期機構

スレッドは, 他のスレッドの終了を待つことができます. See section スレッドの終了の Wait. を参照し てください.

Thread-Specific データの概要

特定の キー に対し, スレッド毎にデータを持つことが出来ます. 任意 のポインタをスレッドに結び付けることが出来ます. 概念的には, Thread-Specific データは, 縦に キー, 横にスレッドが並んだ二次元 配列と考えることが出来ます.

それぞれのスレッドは, 任意のデータを任意の値を自分自身の Thread-Specific データ領域に格納し, 取得することができます.

Thread-Specific データを用いるためには, まず, 全てのスレッドで共有する, ユニークな キー を生成しなければなりません. このためには, pthread_key_create() を用います. この関数は, キー を生成 し, 存在する全てのスレッドの, キー と結び付いた Thread-Specific データの値を 0 に初期化します. また, この関数では, キー に対する デストラクタ関数を定義できます. この関数は, スレッドが終了する際に, Thread-Specific データを開放するために用いられます. (See section スレッドの終了.)

Thread-Specific データを格納するためには, pthread_setspecific() を用います.

Thread-Specific データを取得するためには, pthread_getspecific() を用います.

スケジューリングの概要

スレッドは, それぞれのScheduling Policy Attribute(See section スケジューリングポリシー.)とScheduling Priority Attribute(See section スケジューリングプライオリティ.)によってスケジュールされます.

スケジューリングポリシー は, スレッドがどのようにスケジューリ ングされるかを指定します.

スケジューリングポリシーとプライオリティの値の範囲の関係は以下のように なっています.

                                        プライオリティ大
PRI_FIFO_MAX (= PRI_RR_MAX)                    ^
                                               |
                                               |
PRI_FIFO_MIN (= PRI_RR_MIN)                    |
PRI_OTHER_MAX                                  |
                                               |
                                               |
PRI_OTHER_MIN                                  v
                                        プライオリティ小

スケジューリングは, 以下の規則によって行なわれます.

SCHED_FIFO

@tindex{SCHED_FIFO}

このポリシーを持つスレッドは CPU を横取り (preempt) されることはありませ ん. SCHED_FIFO のスレッドは, 以下の場合に CPU を明け渡し, (存在す れば)他のスレッドを実行します.

SCHED_RR

@tindex{SCHED_RR}

このポリシーは SCHED_FIFO と似ていますが, 同一のプライオリティに 複数のスレッドが存在した場合, それらのスレッドの間で CPU の横取り (preemption) が行なわれます. つまり, タイムスライスを消費すると強制的に コンテキストスイッチが行なわれます.

SCHED_OTHER

@tindex{SCHED_OTHER}

このスケジューリングは, POSIX1003.4a では処理系依存とされています. PTL では, プライオリティに応じて CPU を与えられるスケジューリングに SCHED_OTHER を割り当てています. このポリシーの下では, スレッドに はプライオリティに比例して CPU 時間が与えられます. このポリシーがデフォ ルトです.

SCHED_RR, SCHED_OTHER スケジューリングのためのタイムスライ スは, 100msec です.

シグナルの概要

それぞれのスレッド毎に シグナルマスク が存在します. スレッドのシ グナルマスクを取得, 変更するためには, POSIX.1 のインタフェースがそのま ま用いられます. シグナルマスクはシグナルの集合で, マスクされたシグナル は, スレッドへの配送がブロックされます.

プロセスは, シグナル毎に, シグナルアクション を保持します. アク ションはプロセス中の全てのスレッドで共有されます. シグナルアクションを 取得, 変更するためには, POSIX.1 のインタフェースがそのまま用いられます.

シグナルの配送時に, シグナルアクションが, 終了(termination), 停止 (stop), 続行(continue) ならば, プロセス全体が終了, 停止, 続行します.

セグメンテーション違反のような, 特定のスレッドに起因するシグナルは, 同期シグナル と呼ばれます. 同期シグナルはそのシグナルを引き起こ したスレッドに配送されます.

一方, kill() や端末からのシグナル (Control-C による SIGINT 等) は, 非同期シグナル と呼ばれます. 非同期シグナルを待つためには, sigwait() を使用します. 詳しくは, See section シグナルの配送. を参照して下さい.

スレッドは, pthread_kill() によって, 同一プロセス内の特定のスレッ ドにシグナルを送ることが出来ます. もし, 受信したスレッドがそのシグナル の配送をブロックしていた場合, シグナルはそのスレッドでペンディングされ ます.

シグナルの配送

シグナルアクションは, プロセス毎に管理されます. アクションは, プロセス 中の全てのスレッドで共有されます. シグナルが配送された時, そのシグナル に対応するアクションが振る舞いを決定します.

以下のシグナルは, 特別に取り扱われます.

シグナルが配送された場合, シグナルアクションが参照されます. シグナルア クションは4種類あり, sigaction(), signal() 関数で設定, 変更できます.

SIG_DFL
@tindex{SIG_DFL} デフォルト動作. OS のデフォルトのシグナルアクションが行なわれます. こ れが全てのシグナルアクションのデフォルトです.
SIG_IGN
@tindex{SIG_IGN} 無視. シグナルは破棄されます.
SIG_SIGWAIT_NP
@tindex{SIG_SIGWAIT_NP} シグナルは, sigwait() へ渡されます. どのスレッドも sigwait() を実行中でない場合, シグナルはペンディングされ, スレッ ドが sigwait() を実行した際に渡されます.
<シグナル捕捉関数>
配送されたシグナルに対して, sigwait() を実行しているスレッドが 存在すれば, シグナルは sigwait() に伝えられます. そのシグナルに対して sigwait() を実行しているスレッドが存在しな ければ, シグナルは プロセス中のシグナルをブロックしていないス レッドの一つ に配送され, シグナル捕捉関数の実行が開始されます. もし, プロセス中の全てのスレッドがシグナルをブロックしていた場合, どれかのス レッドがシグナルの配送をアンブロックするか, 対応するシグナルアクション を無視 (SIG_IGN) に設定するまで, シグナルはプロセスでペンディングされま す. シグナル捕捉関数がリターンしたら, スレッドは割り込まれた場所から 実行を再開します. シグナル捕捉関数を実行するスレッドは無作為に選ばれま す.

複数のスレッドが同一のシグナルに対して sigwait() を実行していた 場合, 単一のスレッドの sigwait() のみがリターンします. その際, sigwait() がリターンするスレッドは無作為に選ばれます.

スレッドへ向けたシグナル

以下のようなシグナルは, スレッドへ直接送られます.

シグナルを受信したスレッドが, シグナルの配送をブロックしていた場合, ス レッドがシグナルの配送をアンブロックするか, 対応するアクションを無視 (SIG_IGN) に設定するまで, シグナルはスレッドでペンディングされます. ス レッドでペンディングされているシグナルが再度発生した場合, 後のシグナル は破棄されます.

一度スレッドでシグナルがペンディングされると, それは他のスレッドに配送 されることはありません.

プロセスへ向けたシグナル

プロセス中の全てのスレッドがシグナルをブロックしていた場合, スレッドが シグナルの配送をアンブロックするか, 対応するシグナルアクションを無視 (SIG_IGN) に設定するまで, シグナルはプロセスでペンディングされます. プ ロセスでペンディングされているシグナルが再度発生した場合, 後のシグナル は破棄されます.

シグナルの状態の継承

pthread_create() によって新たなスレッドが生成される際, シグナル の状態は以下のように pthread_create() を呼び出したスレッドから 継承されます.

  1. シグナルブロックマスクは, 作成するスレッドから継承されます.
  2. シグナルペンディング集合はクリアされます.

同期シグナルリスト

PTL では, 以下のシグナルを同期シグナルとして扱います.

SIGILL, SIGFPE, SIGBUS, SIGSEGV, SIGSYS, SIGPIPE, SIGEMT

(SIGPIPE を同期シグナルとして扱うのは問題があるかも知れません)

これら以外のシグナルはすべて非同期シグナルとして扱われます.

Async Safe 関数

非同期に呼び出されるシグナル捕捉関数から安全に呼ぶことの出来る関数 は, Async safe 関数と呼ばれます. Async safe 関数は, 割り込ま れたスレッドが実行中だった関数と衝突することなく, シグナルハンドラ から安全に呼び出すことが出来る関数です.

ANSI C の Async safe 関数は section 4.2 の全ての文字操作関数, section 4.5 の全ての数学関数, section 4.10 の abs() 関数, section 4.11 の全ての文字列関数を含んでいます.

POSIX.1 の Async safe 関数は, section 3.3.1.3 にリストされていま す.

POSIX.4a で導入された Async safe 関数は, pthread_mutex_trylock(), pthread_cond_signal(), pthread_cond_broadcast() です.

内部で使用しているシグナル

以下のシグナルはライブラリ内部で使用しているため, ユーザは使用できませ ん. kill(1) 等によってプロセスにシグナルが送られた場合の動作は不定とな ります.

SIGUSR2, SIGSEGV, SIGILL, SIGALRM

ただし, SIGALRM は, alarm() によって疑似的に発生させることが出 来ます.

シグナルハンドラ

スレッドのシグナルハンドラは, 以下のような引数を受け取ります. @tindex{siginfo}

struct siginfo {
    int si_signo;       /* signal number */
    int si_code;        /* code (machine dependent) */
};

handler(int sig, struct siginfo *info) {
   ...
}

siginfo 構造体の, si_signo メンバーは, シグナルの番号 を保持しています. si_code メンバーは, アーキテクチャ依存の数が 入ります. これは, BSD UNIX のシグナルハンドラの第2引数の値です. pthread_kill(), raise() 等によって起動されたシグナルハン ドラでは, si_code は 0 になります.

errno

PTLでは, 大域変数 errno は, スレッド毎に保持され,コン テキストスイッチのたびに待避, 復元されます. シグナルハンドラを実行する 際も, errno は待避, 復元されるので, ユーザはシグナルハンドラの 内部で errno を保存する必要はありません.

キャンセルの概要

キャンセル は, 実行中のスレッドを他のスレッドから中止させるため の機構です. スレッドキャンセル機構によって, コントロールされた方法でプ ロセス内の他のスレッドの実行を終了させることができます. スレッドは他の スレッドからのキャンセルの要求を一時的に保留したり, キャンセルの際に Cleanup ハンドラを実行することが出来ます.

それぞれのスレッド毎に2ビットで表される Interruptibility state が存在します. 1ビットは, スレッドがキャンセル要求を受け付ける(デフォル ト) か否かを表します. 残りの1ビットは,スレッドが同期キャンセルモード (デフォルト) か, 非同期キャンセルモードかを表します. (See section Interruptibility States.)

スレッドにキャンセル要求を行なうためには, ptread_cancel() を用 います. この関数を呼び出しても, 対象のスレッドが直ちにキャンセルされる わけではありません. また, 一度キャンセル要求を行なった場合, 取り消すこ とは出来ません.

キャンセルが実行されるためには, 対象のスレッドがキャンセル要求を受け 付けるモードであることが必要で, かつ, 対象のスレッドが割り込みポイン ト (See section 割り込みポイント.) を通るか, 対象のス レッドが非同期キャンセルモードの時にのみ発生します. キャンセル要求の なされたスレッドがこれらの条件を満たせば, スレッドは次にスケジューリ ングされた時にキャンセルされます.

開放されなければならないリソース (Mutex 等) を保持している間に非同期キャ ンセルモードを用いることは, キャンセルによってリソースの開放が出来なく なる可能性があるため, 非常に危険です. また, Cleanup スタックはスレッド が同期キャンセルモードか, キャンセルを禁止した状態の時のみ, 安全に操作 (プッシュ, ポップ) することができます. (See section スレッド Cleanup.) 非同期キャンセルモードは, 長時間かかる計算のループ などを中止するためだけに用いるべきです.

Interruptibility States

Interruptibility state は, スレッドがキャンセルの要求を受け取っ た際の動作を決定します.

それぞれのスレッドは, 2ビットの Interruptibility states を持って います.

キャンセル許可フラグ
@tindex{PTHREAD_INTR_DISABLE} @tindex{PTHREAD_INTR_ENABLE} キャンセル許可フラグは, キャンセル要求を受け付ける (PTHREAD_INTR_ENABLE,デフォルト) か否か (PTHREAD_INTR_DISABLE) を制御 します. キャンセル許可モードを変更するためには pthread_setintr() を用います. キャンセルは, 対象のスレッドが PTHREAD_INTR_ENABLE の場合にのみ発生し ます. PTHREAD_INTR_DISABLE の場合, キャンセル要求はスレッドでペンディ ングされ, 後にキャンセル許可モードが PTHREAD_INTR_ENABLE に変更された ときにキャンセルが実行されます.
キャンセルタイプ
@tindex{PTHREAD_INTR_ASYNCHRONOUS} @tindex{PTHREAD_INTR_CONTROLLED} キャンセルタイプは, スレッドが特定の操作を実行中にのみキャンセルされる (PTHREAD_INTR_CONTROLLED) --同期キャンセルモード(デフォルト)-- か, 「何を実行していても」キャンセルされる (PTHREAD_INTR_ASYNCHRONOUS) -- 非同期キャンセルモード-- かを制御します.

キャンセル許可フラグが PTHREAD_INTR_ENABLE で, キャンセルタイプが PTHREAD_INTR_CONTROLLED ならば, キャンセル要求は, スレッドの実行 が割り込みポイントに達するまでペンディングされます. (See section 割り込みポイント.)

キャンセル許可フラグが PTHREAD_INTR_ENABLE で, キャンセルタイプが PTHREAD_INTR_ASYNCHRONOUS ならば, 新たな, あるいはペンディングさ れていたキャンセル要求は直ちに処理されます.

キャンセル許可フラグが PTHREAD_INTR_DISABLE ならば, 全てのキャン セル要求はペンディングされるのでキャンセルタイプの設定は直ちには効果はあ りませんが, キャンセル許可フラグが ENABLE になると, 設定されたキャンセル タイプは効果を持ちます.

新たに生成されるスレッドと, 初期スレッドのキャンセル許可フラグとキャンセ ルタイプの初期値はそれぞれ PTHREAD_INTR_ENABLE, PTHREAD_INTR_CONTROLLED です.

割り込みポイント

以下は割り込みポイントとなります.

スレッドのキャンセル許可モードが PTHREAD_INTR_ENABLE で, スレッドがこ れらの関数を呼び出してブロックしている間に, 他のスレッドからキャンセル 要求がなされると, スレッドはキャンセルされます.

明示的にキャンセルの要求があったかどうかをテストするためには, pthread_testintr() を用います. スレッドがキャンセル許可モード ENABLE で, そのスレッドにキャンセル要求がなされている時に, スレッドが pthread_testintr() を実行すると, キャンセルが実行されます. pthread_tesetintr() からはリターンしません.

割り込みポイントで待っているイベントが起こった後, スレッドがスケジュー リングされる前に, キャンセル要求が発生した場合, キャンセルが実行される か, あるいはキャンセル要求がペンディングされて, スレッドが通常の実行を 開始するかどうかは, 不定です.

スレッド Cleanup

スレッドがキャンセルされる際に, 後処理をするルーチン (Cleanup ハ ンドラ)を設定できます. スレッドは Cleanup ハンドラ中でリソースを自動 的に開放したりすることができます.

スレッドは Cleanup ルーチンのリストを保持しています. ルーチンを登録す るためには, pthread_cleanup_push(), pthread_cleanup_push_f_np() を, ルーチンを開放するためには, pthread_cleanup_pop(), pthread_cleanup_pop_f_np() を用います.

キャンセルが実行される際, リスト上のルーチンは LIFO (Last In--First Out) の順に, 一つずつ実行されます. すなわち, 最後にリストにプッシュさ れた関数が最初に実行されます. Cleanup ルーチンを実行するスレッドは最 後の Cleanup ルーチンがリターンするまでキャンセル許可モードは DISABLE になります. 最後の Cleanup ルーチンがリターンすると, スレッドの実行は 終了し, 終了ステータスとして ((void*)-1) が join しているスレッド に返されます.

Cleanup ルーチンはスレッドが pthread_exit() を呼びだした場合に も実行されます.

Condition Variable での Wait 中のキャンセルでは副作用として, 最初の Cleanup ルーチンが呼ばれる前に, Mutex が再び獲得されます. さらに, Condition Wait 中にキャンセル要求のあったスレッドは Condition で Wait しているとは見なされず, スレッドは pthread_cond_signal() の対象 とはなりません.

Interrupt Safe 関数

非同期キャンセルモードのスレッドが安全に呼び出すことが出来る関数は Interrupt Safe 関数 といいます. また, Interrupt safe 関数 は, 割り込まれた時にも呼ぶことが出来ます. Interrupt Safe 関数は, 大域 データを書き換えたりすることが無く, 関数の実行が途中で中止されても安全 な関数です.

C の標準関数は, Interrupt Safe 関数として, section 4.2 の全ての 文字操作関数, section 4.5 の全ての数学関数, section 4.10 の abs() 関数, section 4.11 の全ての文字列関数を含んでいます.

POSIX.4a で導入された Interrupt Safe 関数は, pthread_cond_signal(), pthread_cond_broadcast() です.

ログ機能について

マルチスレッドのプログラムをデバッグすることは, 簡単ではありません. デ バッグを助けるため, PTL にはスレッドの振る舞いを記録し, ログファイルに 書き出す機能があります. 出力されたログファイルは, 専用のログビューアを 用いて X Window 上で見ることができます.

ログビューアは, `contrib' ディレクトリ中の `xptllogXXX.tar.gz' にあります. 詳細は, アーカイブ中のドキュメン トを参照してください.

環境変数 PTHREAD_LOG に, ログファイル名をセットして, プログラムを走 らせると, 環境変数で指定されたファイルにログが出力されます. 環境変数 PTHREAD_LOG が存在しなければ, ログは出力されません.

ログファイルはホストのバイトオーダーに依存しないようになっています.

ログには, 以下の情報が記録されます.

スレッド, Mutex, Condition Variable に名前を付けるとログビューアで表示 されるため, デバッグを効率的に行なうことが出来ます.

ユーザが任意の ASCII 文字列をログに出力することも可能です. これは従来 の printf デバッグに対応するものと考えて良いでしょう. このためには, 関数 pthread_log_np を用います. ここで指定された文字列はログビュー アの画面上で表示されます.

ログはある程度たまる毎にファイルに書き出されますが, この処理の間, 他の スレッドの実行は停止されます. ログファイルの書きだしには比較的時間がか かるため, ログをファイルに書き出す直前に, PTL は文字列[log flushing] を書き出して, 書き出し中であることをユーザが認識できるよう にしています.

データ型

Pthreads では, 以下のデータ型を定義しています. これらはすべて `<pthread.h>' で定義されます.

pthread_t
スレッド
pthread_attr_t
スレッドアトリビュートオブジェクト
pthread_mutex_t
Mutex
pthread_mutexattr_t
Mutex アトリビュートオブジェクト
pthread_cond_t
Condition Variable
pthread_condattr_t
Conditionアトリビュート
pthread_key_t
Thread-Specific データキー
pthread_once_t
動的なパッケージの初期化
pthread_protocol_t
Mutexのプロトコル

PTL では, pthread_mutex_tpthread_cond_t を除く全ての型 は intと同一で, 値をハンドルとして用いています. このため, エラーチェック が可能となっています. pthread_mutex_tpthread_cond_t は 構造体です. これは高速化のためです.

@tindex{pthread_t} @tindex{pthread_attr_t} @tindex{pthread_mutexattr_t} @tindex{pthread_condattr_t} @tindex{pthread_cond_t} @tindex{pthread_key_t} @tindex{pthread_protocol_t}

関数の戻り値

特に断わりの無い限り, int を返す Pthread の関数は正常に終了した場合 0 を返し, 失敗した場合 -1 を返します. 失敗した場合は, 変数 errno に エラーの原因が入っています. (See section errno.)

また, ポインタを返す Pthread の関数は, 失敗した場合にヌルポインタを返 します.

注意点

ここでは, マルチスレッド環境で注意すべき点について述べます.

大域変数の保護

CPU がタイムスライスによって奪われるスケジューリング (SCHED_RR, SCHED_OTHER) の下では, 複数のスレッドから同時にアクセスされる大域 変数や関数内 static 変数は, 排他制御されなければなりません (See section 同期機構の概要.). これに対し, auto 変数は通常, 排他制御する必要は有りません.

デッドロック

マルチスレッド環境では, 不注意によって簡単にデッドロックが起きます. 例 えば以下のようなコードはデッドロックを引き起こします. (Thread1 が mutex1 をロックした後, Thread2 が mutex2 をロックすると, Thread2 は mutex1 をロックできずにブロックし, Thread1 も mutex2 をロックできずに ブロックしてしまいます.)

Thread.1                            Thread.2
while(1) {                             while(1) {
  pthread_mutex_lock( &mutex1 );          pthread_mutex_lock( &mutex2 );
  ....                                    ....
  pthread_mutex_lock( &mutex2 );          pthread_mutex_lock( &mutex1 );
  ....                                    ....
  pthread_mutex_unlock( &mutex2 );        pthread_mutex_unlock( &mutex1 );
  pthread_mutex_unlock( &mutex1 );        pthread_mutex_unlock( &mutex2 );
}                                      }

複数の Mutex をロックする場合, デッドロックを避けるために, プロセス内 で Mutex をロックする順序を統一しなければなりません.

既存のライブラリの使用

既存のライブラリのほとんどは, マルチスレッド環境から呼ぶには適していま せん. それらのほとんどは, (適切なロック無しに)大域データにアクセスして います.

これらのライブラリを使用するためには, いくつかの方法があります.

PTLでは, いくつかの既存のライブラリ関数のリエントラント版を用 意しています. See section リエントラント関数. を参照し てください.

PTLの, スレッド対応標準入出力ライブラリ(stdio)は, ファイルデ スクリプタ毎に用意した Mutex をロックすることによって実現しています.

スレッドスタック

スレッドのスタックは, プロセス中のデータ領域あるいは共有メモリ領域に確 保されます.

共有メモリスタック及び Redzone プロテクトスタックは, 溢れると

[Thread 6]Stack Type: shared memory
Stack overflow (cannot extend) stack size 16384, bottom 0x20400000

のように表示して停止します. これに参考に, スタックのサイズを変更して下 さい.

入出力

I/O システムコールは, ほとんどがライブラリによってオーバーライドされて います.

それぞれのスレッドは ネットワーク I/O やプロセス間通信を並行して実行でき ます. I/O 操作がすぐに完了しないばあい, その I/O を実行しようとしたスレッ ドのみがブロックされ, 他のスレッドの実行は続行されます. (ただし, ファイ ル I/O は, ファイル I/O が完了するまで全てのスレッドをブロックさせます.)

これらは, ライブラリ内部でファイルデスクリプタを非ブロックモードで使用 することによって実現しています. 現在のところ, ユーザは, 非ブロックモー ドを使用することは出来ません.

標準入出力, 標準エラー出力も非ブロックモードで使用しています. このこと は, シェルを混乱させるかも知れません. See section ジョブコントトロール.

FIFO, TLI (Transport Layer Interface), ファイルロッキング 等はサポートさ れていません. これらを利用した場合, どうなるかは不明です.

ジョブコントトロール

PTLは, ジョブコントロール制御には以下のように対応しています.

プロセスが停止(サスペンド)された場合は, 全てのスレッドが停止します. (See section シグナルの配送.)

プロセスが background で走行中に, スレッドが read() 等によって制 御端末からの入力が発生すると, そのスレッドは, プロセスが foreground に 回されて入力が完了するまでブロックします.

プロセスが background で走行中に, write() 等によって制御端末への 出力が発生すると, 端末がバックグラウンドジョブからの出力を禁止している場 合でも(例えば stty tostop を実行している等), 現在のところ出力は 行なわれます.

PTLではプロセスが, backgroud から foreground に回ったことを知 るために, SIGCONT シグナルが送られることを前提としています. これは, 大 抵のシェルには当てはまりません(確認した限りでは最近の tcsh (6.03 以降?) が SIGCONT を送るようです). 動いているプロセスに SIGCONT を送ることは特 に害が無いため, シェルに手を加えてプロセスが foreground に回った際に SIGCONT を送るようにすべきです. bash に関しては foreground に回した際 に SIGCONT を送るようにするパッチが `patches' ディレクトリにあり ます.

PTLでは, 標準出力, 標準エラー出力を, 非ブロックモードで使用し ています. これは, シェルを混乱させる場合があります(例えば version 1.13.4 より前の bash). PTLでは, シェルが混乱しないように最小 限の対策を行なっていますが, プロセスを停止(サスペンド)するために, SIGTSTP でなく SIGSTOP を送ったり, プロセスが捕捉できないシグナルを受 け取って死んでしまうと, シェルは混乱します.

移植性

ライブラリで定義している関数, 定数のうち, _np, あるいは _NP で終わるものは独自のもので, 他の POSIX 1003.4a ベースのライブ ラリでは提供されません. これらの関数の使用は移植性を低下させます. 注意し てください.

NP は, Non Portable の略です.

PTL では `<pthread.h>' 中に, __PTL__ を定義しています. 移 植性が問題となる場合, #ifdef によってコードを切り分けてください.


Go to the first, previous, next, last section, table of contents.