pthreads(7) 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(2))
-
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))

pthreads 関数の返り値

ほとんどの pthreads 関数は成功すると 0 を返し、 失敗した場合エラー番号を返す。 pthreads 関数は errno をセットしない点に注意すること。 POSIX.1-2001 では、 エラーを返す可能性のある pthreads 関数がエラー EINTR で失敗することは決してないと規定している。

スレッド ID

あるプロセス内の各スレッドは (pthread_t 型の) 一意なスレッド識別子を持つ。 この識別子は、 pthread_create(3) の呼び出し元に返される。また、スレッドは自身のスレッド識別子を pthread_self(3) を使って取得できる。 スレッド ID の一意性が保証されるのは、一つのプロセス内においてのみである。 終了したスレッドが join された後では、スレッド ID は再利用される可能性がある。 スレッド ID を引き数に取る全てのスレッド関数において、 その ID は呼び出し元と同じプロセス内の一つのスレッドを参照する。

スレッドセーフな関数

スレッドセーフな関数は、複数のスレッドから同時に呼び出しても安全な (すなわち、同時に呼び出されたかに関わらず、同じ結果を返す) 関数のことである。

POSIX.1-2001 と POSIX.1-2008では、一部の例外を除き、 標準で規定されている全ての関数がスレッドセーフであることを要求している。 以下の関数が例外である。

asctime()
basename()
catgets()
crypt()
ctermid() (NULL でない引き数を渡された場合)
ctime()
dbm_clearerr()
dbm_close()
dbm_delete()
dbm_error()
dbm_fetch()
dbm_firstkey()
dbm_nextkey()
dbm_open()
dbm_store()
dirname()
dlerror()
drand48()
ecvt() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
encrypt()
endgrent()
endpwent()
endutxent()
fcvt() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
ftw()
gcvt() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
getc_unlocked()
getchar_unlocked()
getdate()
getenv()
getgrent()
getgrgid()
getgrnam()
gethostbyaddr() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
gethostbyname() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
gethostent()
getlogin()
getnetbyaddr()
getnetbyname()
getnetent()
getopt()
getprotobyname()
getprotobynumber()
getprotoent()
getpwent()
getpwnam()
getpwuid()
getservbyname()
getservbyport()
getservent()
getutxent()
getutxid()
getutxline()
gmtime()
hcreate()
hdestroy()
hsearch()
inet_ntoa()
l64a()
lgamma()
lgammaf()
lgammal()
localeconv()
localtime()
lrand48()
mrand48()
nftw()
nl_langinfo()
ptsname()
putc_unlocked()
putchar_unlocked()
putenv()
pututxline()
rand()
readdir()
setenv()
setgrent()
setkey()
setpwent()
setutxent()
strerror()
strsignal() [POSIX.1-2008 で追加された]
strtok()
system() [POSIX.1-2008 で追加された]
tmpnam() (NULL でない引き数を渡された場合)
ttyname()
unsetenv()
wcrtomb() (最後の引き数が NULL の場合)
wcsrtombs() (最後の引き数が NULL の場合)
wcstombs()
wctomb()

async-cancel-safe 関数

async-cancel-safe 関数は、 非同期キャンセル機能が有効になっているアプリケーションで 安全に呼び出すことができる関数のことである (pthread_setcancelstate(3) を参照)。

以下の関数だけが、POSIX.1-2001 と POSIX.1-2008 で async-cancel-safe で なければならないとされている。

pthread_cancel()
pthread_setcancelstate()
pthread_setcanceltype()

取り消しポイント (cancellation points)

POSIX.1 の規定では、特定の関数は取り消しポイントでなければならず、 他の特定の関数は取り消しポイントであってもよいとされている。 あるスレッドが取り消し可能で、その取り消し種別 (cancelability type) が延期 (deferred) で、そのスレッドに対する取り消し要求が処理待ちの場合、 取り消しポイントである関数を呼び出した時点で、そのスレッドのキャンセルが 行われる。

POSIX.1-2001 と POSIX.1-2008 の両方、もしくはいずれか一方では、 以下の関数は、取り消しポイント (cancellation points) で あることが必須となっている。

accept()
aio_suspend()
clock_nanosleep()
close()
connect()
creat()
fcntl() F_SETLKW
fdatasync()
fsync()
getmsg()
getpmsg()
lockf() F_LOCK
mq_receive()
mq_send()
mq_timedreceive()
mq_timedsend()
msgrcv()
msgsnd()
msync()
nanosleep()
open()
openat() [POSIX.1-2008 で追加された]
pause()
poll()
pread()
pselect()
pthread_cond_timedwait()
pthread_cond_wait()
pthread_join()
pthread_testcancel()
putmsg()
putpmsg()
pwrite()
read()
readv()
recv()
recvfrom()
recvmsg()
select()
sem_timedwait()
sem_wait()
send()
sendmsg()
sendto()
sigpause() [POSIX.1-2001 only (moves to "may" list in POSIX.1-2008)]
sigsuspend()
sigtimedwait()
sigwait()
sigwaitinfo()
sleep()
system()
tcdrain()
usleep() [POSIX.1-2001 のみ (POSIX.1-2008 で削除された)]
wait()
waitid()
waitpid()
write()
writev()

POSIX.1-2001 と POSIX.1-2008 の両方、もしくはいずれか一方では、 以下の関数は、取り消しポイント (cancellation points) で あってもよいことになっている。

access()
asctime()
asctime_r()
catclose()
catgets()
catopen()
chmod() [POSIX.1-2008 で追加された]
chown() [POSIX.1-2008 で追加された]
closedir()
closelog()
ctermid()
ctime()
ctime_r()
dbm_close()
dbm_delete()
dbm_fetch()
dbm_nextkey()
dbm_open()
dbm_store()
dlclose()
dlopen()
dprintf() [POSIX.1-2008 で追加された]
endgrent()
endhostent()
endnetent()
endprotoent()
endpwent()
endservent()
endutxent()
faccessat() [POSIX.1-2008 で追加された]
fchmod() [POSIX.1-2008 で追加された]
fchmodat() [POSIX.1-2008 で追加された]
fchown() [POSIX.1-2008 で追加された]
fchownat() [POSIX.1-2008 で追加された]
fclose()
fcntl() (cmd 引き数が何であっても)
fflush()
fgetc()
fgetpos()
fgets()
fgetwc()
fgetws()
fmtmsg()
fopen()
fpathconf()
fprintf()
fputc()
fputs()
fputwc()
fputws()
fread()
freopen()
fscanf()
fseek()
fseeko()
fsetpos()
fstat()
fstatat() [POSIX.1-2008 で追加された]
ftell()
ftello()
ftw()
futimens() [POSIX.1-2008 で追加された]
fwprintf()
fwrite()
fwscanf()
getaddrinfo()
getc()
getc_unlocked()
getchar()
getchar_unlocked()
getcwd()
getdate()
getdelim() [POSIX.1-2008 で追加された]
getgrent()
getgrgid()
getgrgid_r()
getgrnam()
getgrnam_r()
gethostbyaddr() [SUSv3 のみ (この関数は POSIX.1-2008 で削除されている)]
gethostbyname() [SUSv3 のみ (この関数は POSIX.1-2008 で削除されている)]
gethostent()
gethostid()
gethostname()
getline() [POSIX.1-2008 で追加された]
getlogin()
getlogin_r()
getnameinfo()
getnetbyaddr()
getnetbyname()
getnetent()
getopt() (opterr が 0 以外の場合)
getprotobyname()
getprotobynumber()
getprotoent()
getpwent()
getpwnam()
getpwnam_r()
getpwuid()
getpwuid_r()
gets()
getservbyname()
getservbyport()
getservent()
getutxent()
getutxid()
getutxline()
getwc()
getwchar()
getwd() [SUSv3 のみ (この関数は POSIX.1-2008 で削除されている)]
glob()
iconv_close()
iconv_open()
ioctl()
link()
linkat() [POSIX.1-2008 で追加された]
lio_listio() [POSIX.1-2008 で追加された]
localtime()
localtime_r()
lockf() [POSIX.1-2008 で追加された]
lseek()
lstat()
mkdir() [POSIX.1-2008 で追加された]
mkdirat() [POSIX.1-2008 で追加された]
mkdtemp() [POSIX.1-2008 で追加された]
mkfifo() [POSIX.1-2008 で追加された]
mkfifoat() [POSIX.1-2008 で追加された]
mknod() [POSIX.1-2008 で追加された]
mknodat() [POSIX.1-2008 で追加された]
mkstemp()
mktime()
nftw()
opendir()
openlog()
pathconf()
pclose()
perror()
popen()
posix_fadvise()
posix_fallocate()
posix_madvise()
posix_openpt()
posix_spawn()
posix_spawnp()
posix_trace_clear()
posix_trace_close()
posix_trace_create()
posix_trace_create_withlog()
posix_trace_eventtypelist_getnext_id()
posix_trace_eventtypelist_rewind()
posix_trace_flush()
posix_trace_get_attr()
posix_trace_get_filter()
posix_trace_get_status()
posix_trace_getnext_event()
posix_trace_open()
posix_trace_rewind()
posix_trace_set_filter()
posix_trace_shutdown()
posix_trace_timedgetnext_event()
posix_typed_mem_open()
printf()
psiginfo() [POSIX.1-2008 で追加された]
psignal() [POSIX.1-2008 で追加された]
pthread_rwlock_rdlock()
pthread_rwlock_timedrdlock()
pthread_rwlock_timedwrlock()
pthread_rwlock_wrlock()
putc()
putc_unlocked()
putchar()
putchar_unlocked()
puts()
pututxline()
putwc()
putwchar()
readdir()
readdir_r()
readlink() [POSIX.1-2008 で追加された]
readlinkat() [POSIX.1-2008 で追加された]
remove()
rename()
renameat() [POSIX.1-2008 で追加された]
rewind()
rewinddir()
scandir() [POSIX.1-2008 で追加された]
scanf()
seekdir()
semop()
setgrent()
sethostent()
setnetent()
setprotoent()
setpwent()
setservent()
setutxent()
sigpause() [POSIX.1-2008 で追加された]
stat()
strerror()
strerror_r()
strftime()
symlink()
symlinkat() [POSIX.1-2008 で追加された]
sync()
syslog()
tmpfile()
tmpnam()
ttyname()
ttyname_r()
tzset()
ungetc()
ungetwc()
unlink()
unlinkat() [POSIX.1-2008 で追加された]
utime() [POSIX.1-2008 で追加された]
utimensat() [POSIX.1-2008 で追加された]
utimes() [POSIX.1-2008 で追加された]
vdprintf() [POSIX.1-2008 で追加された]
vfprintf()
vfwprintf()
vprintf()
vwprintf()
wcsftime()
wordexp()
wprintf()
wscanf()

実装時に、標準規格で規定されていないその他の関数を取り消しポイント とすることも認められている。 特に、停止 (block) する可能性がある非標準の関数を取り消しポイントと する実装はあり得ることだろう (ファイルを扱う可能性のあるほとんどの関数がこれに含まれる)。

Linux でのコンパイル

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

POSIX スレッドの Linux での実装

これまで、2つのスレッドの実装が Linux の GNU C ライブラリにより 提供されてきた。
LinuxThreads
最初の Pthreads の実装。 glibc 2.4 以降は、この実装はもはやサポートされていない。
NPTL (Native POSIX Threads Library)
新しい Pthreads の実装。LinuxThreads と比べると、 NPTL は POSIX.1 の要求仕様への準拠の度合いが高く、 多数のスレッドを作成した際の性能も高い。 NPTL は glibc 2.3.2 以降で利用可能である。 NPTL を利用するには Linux 2.6 カーネルに実装されている機能が必要である。

どちらの実装もいわゆる 1:1 実装、すなわち個々のスレッドが カーネルのスケジューリング実体にマッピングされる。 どちらのスレッドの実装も Linux の clone(2) システムコールを利用している。 NPTL では、スレッド同期の基本機構 (mutex や スレッドの join 等) は Linux の futex(2) システムコールを使って実装されている。

LinuxThreads

この実装の大きな特徴は以下の通りである:
-
メインスレッド (最初のスレッド) とプログラムが pthread_create(3) を使って作成したスレッドに加え、 この実装では「管理 (manager)」スレッドが作成される。 管理スレッドはスレッドの作成と終了を取り扱う (このスレッドがうっかり kill されると、問題が起こることがある)。
-
この実装では内部でシグナルを使用している。 Linux 2.2 以降では、リアルタイムシグナルのうち最初の 3つが使われる (signal(7) 参照)。 それ以前のカーネルでは SIGUSR1SIGUSR2 が使われる。 アプリケーションは、スレッド実装で利用されているシグナルを どれも使わないようにしなければならない。
-
スレッド間でプロセス 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つの番号を使用しており (signal(7) 参照)、これらのシグナルはアプリケーションでは使用できない。

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 の両方をサポートしている glibc (glibc 2.3.x) があるシステムでは、 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

この文書について

この man ページは Linux man-pages プロジェクトのリリース 3.65 の一部 である。プロジェクトの説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。