名前


pthreads - POSIX スレッド

説明


POSIX.1 は、一般に POSIX スレッドや Pthreads として知られる スレッド・プログラミングのインタフェース群 (関数、ヘッダファイル) を規定している。一つのプロセスは複数のスレッドを持つことができ、 全てのスレッドは同じプログラムを実行する。 これらのスレッドは同じ大域メモリ (データとヒープ領域) を共有するが、 各スレッドは自分専用のスタック (自動変数) を持つ。

POSIX.1 はスレッド間でどのような属性を共有するかについても定めている (つまり、これらの属性はスレッド単位ではなくプロセス全体で共通である):
- プロセス ID
- 親プロセス ID
- プロセスグループ ID とセッション ID
- 制御端末
- ユーザ ID とグループ ID
- オープンするファイルディスクリプタ
- レコードのロック (fcntl(3) 参照)
- シグナルの配置
- ファイルモード作成マスク (umask(2))
- カレント・ディレクトリ (chdir(2)) とルート・ディレクトリ (chroot(2))
- インターバル・タイマ (setitimer(2)) と POSIX タイマ (timer_create(3))
- nice 値 (setpriority(2))
- リソース制限 (setrlimit(2))
- CPU 時間 (times(2)) とリソース (getrusage(2)) の消費状況の計測
スタックについても、POSIX.1 はどのような属性が 個々のスレッドで独立に管理されるかを規定している:
- スレッド ID pthread_t データ型)
- シグナルマスク (pthread_sigmask(3))
- errno 変数
- 代替シグナルスタック (sigaltstack(2))
- リアルタイム・スケジューリングのポリシーと優先度 (sched_setscheduler(2) と sched_setparam(2))
以下の Linux 特有の機能もスレッド単位である:
- ケーパビリティ (capabilities(7) 参照)
- CPU affinity (親和度) (sched_setaffinity(2))

Linux でのコンパイル

Linux では、Pthreads API を用いたプログラムは cc -pthread でコンパイルすべきである。

POSIX スレッドの Linux での実装

これまで、2つのスレッドの実装が Linux の GNU C ライブラリにより 提供されてきた。
- LinuxThreads 最初の (今は時代遅れになった) Pthreads の実装。
- NPTL (Native POSIX Threads Library) 新しい Pthreads の実装。LinuxThreads と比べると、 NPTL は POSIX.1 の要求仕様への準拠の度合いが高く、 多数のスレッドを作成した際の性能も高い。 NPTL は Linux 2.6 カーネルに実装されている機能を必要とする。
どちらの実装もいわゆる 1:1 実装、すなわち個々のスレッドが カーネルのスケジューリング実体にマッピングされる。

どちらのスレッドの実装も Linux の clone(2) システムコールを利用している。 NPTL では、スレッド同期の基本機構 (mutex や スレッドの join 等) は Linux の futex(2) システムコールを使って実装されている。

新しい GNU C ライブラリは LinuxThreads と NPTL の両方を提供しており、 (カーネルがサポートしていれば) NPTL がデフォルトとなる。

LinuxThreads

この実装の大きな特徴は以下の通りである:
- メインスレッド (最初のスレッド) とプログラムが pthread_create(3) を使って作成したスレッドに加え、 この実装では「管理 (manager)」スレッドが作成される。 管理スレッドはスレッドの作成と終了を取り扱う (このスレッドがうっかり kill されると、問題が起こることがある)。
- この実装では内部でシグナルを使用している。 Linux 2.2 以降では、リアルタイムシグナルのうち最初の 3つが使われる。 それ以前のカーネルでは SIGUSR1 と SIGUSR2 が使われる。 アプリケーションは、スレッド実装で利用されているシグナルを どれも使わないようにしなければならない。
- スレッド間でプロセス ID を共有しない (実際には LinuxThreads のスレッドは通常よりは情報を共有するプロセスとして 実装されているが、一つの共通のプロセス ID を共有してはいない)。 (管理スレッドを含む) LinuxThreads スレッドは ps(1) を使うと別のプロセスのように見える。
LinuxThreads の実装では POSIX.1 仕様から逸脱している点が いくつかある。以下に示すような点がある:
- getpid(2) を呼び出したときに、スレッド毎に異なる値が返される。
- メインスレッド以外のスレッドで getppid(2) を呼び出すと、管理スレッドのプロセス ID が返される。 本当は、これらのスレッドで getppid(2) を呼んだ場合にはメインスレッドでの getppid(2) と同じ値が返るべきである。
- あるスレッドが fork(2) を使って新しい子プロセスを作成した場合、 どのスレッドでもこの子プロセスを wait(2) できるべきである。しかしながら、この実装では子プロセスを作成した スレッドだけがこの子プロセスを wait(2) できる。
- あるスレッドが execve(2) を呼び出した場合、他のスレッドは全て終了される (POSIX.1 の仕様通り)。 しかしながら、新しいプロセスは execve(2) を呼んだスレッドと同じ PID を持つ。正しくは メインスレッドと同じ PID を持つべきである。
- スレッド間でユーザ ID とグループ ID が共有されない このことは、set-user-ID プログラムで面倒な事態を招いたり、 アプリケーションが seteuid(2) などを使って信用情報 (credentials) を変更した場合に Pthreads 関数が失敗する原因となる。
- スレッド間で共通のセッション ID やプロセスグループ ID を共有しない。
- スレッド間で fcntl(2) を使って作成されるレコード・ロックを共有しない。
- times(2)getrusage(2) が返す情報がプロセス全体の情報でなくスレッド単位の情報である。
- スレッド間でセマフォのアンドゥ値 (semop(2) 参照) を共有しない。
- スレッド間でインターバル・タイマを共有しない。
- スレッドは共通の nice 値を共有しない。
- POSXI.1 では、全体としてのプロセスに送られるシグナルと、 個別のスレッドに送られるシグナルを区別して考えている。 POSIX.1 によると、プロセスに送られたシグナル (例えば kill(2) を使って送る) は、そのプロセスに属すスレッドのうち 勝手に (arbitrarily) に選択された一つのスレッドにより処理される ことになっている。LinuxThreads はプロセスに送られるシグナルの 概念に対応しておらず、シグナルは特定のスレッドにだけ送ることができる。
- スレッドはそれぞれの独自の代替シグナルスタックの設定を持つ。 しかし、新しいスレッドの代替シグナルスタックの設定は そのスレッドを作成したスレッドからコピーされ、そのため スレッドは最初は一つの代替シグナルスタックを共有する。 (仕様では、新しいスレッドは代替シグナルスタックが定義されていない状態 で開始されるべきとされている。 2つのスレッドが共有されている代替シグナルスタック上で同時に シグナルの処理を行った場合、予測不可能なプログラムのエラーが 起こり得る。)

NPTL

NPTL では、一つのプロセスの全てのスレッドは同じスレッド・グループ に属する; スレッド・グループの全メンバーは同じ PID を共有する。 NPTL は管理スレッド (manager thread) を利用しない。 NPTL は内部でリアルタイムシグナルのうち最初の 2つの番号を使用しており、 これらのシグナルはアプリケーションでは使用できない。

NPTL にも POSIX.1 に準拠していない点がいくつかある:
- スレッドは共通の nice 値を共有しない。
NPTL の標準非準拠な点のうちいくつかは以前のカーネルでのみ発生する:
- times(2)getrusage(2) が返す情報がプロセス全体の情報でなくスレッド単位の情報である (カーネル 2.6.9 で修正された)。
- スレッド間でリソース制限を共有しない (カーネル 2.6.10 で修正された)。
- スレッド間でインターバル・タイマを共有しない (カーネル 2.6.12 で修正された)。
- メインスレッドだけが setsid(2) を使って新しいセッションを開始することができる (カーネル 2.6.16 で修正された)。
- メインスレッドだけが setpgid(2) を使ってそのプロセスをプロセス・グループ・リーダーにすることができる (カーネル 2.6.16 で修正された)。
- スレッドはそれぞれの独自の代替シグナルスタックの設定を持つ。 しかし、新しいスレッドの代替シグナルスタックの設定は そのスレッドを作成したスレッドからコピーされ、そのため スレッドは最初は一つの代替シグナルスタックを共有する (カーネル 2.6.16 で修正された)。
NPTL の実装では以下の点についても注意すること:
- スタックサイズのリソースのソフト・リミット (setrlimit(2) の RLIMIT_STACK の説明を参照) が unlimited 以外の値に設定されている場合、ソフト・リミットの値が 新しいスレッドのデフォルトのスタックサイズとなる。 設定を有効にするためには、プログラムを実行する前にリミット値を 設定しておかなければならない。たいていは、シェルの組み込みコマンドの ulimit -s (C シェルでは limit stacksize) を使って設定する。

スレッド実装の判定

glibc 2.3.2 以降では、 getconf(1) コマンドを使って、 システムのデフォルトのスレッド実装を判定することができる。 以下に例を示す:

bash$ getconf GNU_LIBPTHREAD_VERSION NPTL 2.3.4

ぞれ以前の glibc のバージョンでは、以下のようなコマンドで デフォルトのスレッド実装を判定することができる。

bash$ $( ldd /bin/ls | grep libc.so | awk ’{print $3}’ ) | \ egrep -i ’threads|ntpl’ Native POSIX Threads Library by Ulrich Drepper et al

スレッドの実装の選択: LD_ASSUME_KERNEL

LinuxThreads と NPTL の両方をサポートしているシステムでは、 LD_ASSUME_KERNEL 環境変数を使うことで、動的リンカがデフォルトで 選択するスレッド実装を上書きすることができる。 この変数により、動的リンカが特定のバージョンのカーネル上で 動作していると仮定するように指定する。 NPTL が必要とするサポート機能を提供していないカーネルバージョンを 指定することで、強制的に LinuxThreads を使うことができる (このようなことをする最もありそうな場面は、 LinuxThreads の標準非準拠な振舞いに依存する (壊れた) アプリケーション を動作させる場合だろう)。 以下に例を示す:

bash$ $( LD_ASSUME_KERNEL=2.2.5 ldd /bin/ls | grep libc.so | \ awk ’{print $3}’ ) | egrep -i ’threads|ntpl’ linuxthreads-0.10 by Xavier Leroy

関連項目


clone(2), futex(2), gettid(2), futex(7), および Pthreads の各種マニュアルページ、例えば: pthread_atfork(3), pthread_cleanup_push(3), pthread_cond_signal(3), pthread_cond_wait(3), pthread_create(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_key_create(3), pthread_kill(3), pthread_mutex_lock(3), pthread_mutex_unlock(3), pthread_once(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_testcancel(3).

openSUSE Logo

コンテンツ