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

第 7 章 コンパイルとデバッグ

この章では、マルチスレッドプログラムのコンパイルとデバッグについて説明します。

マルチスレッドアプリケーションのコンパイル

ヘッダファイル、定義フラグ、リンクなどについては、オプションが多数あります。

コンパイルの準備

マルチスレッドプログラムのコンパイルとリンクには、次のものが必要です。C コンパイラ以外は、Solaris オペレーティング環境に付属しています。

セマンティクスの選択 − Solaris または POSIX

一部の関数 (表 7-1 に示した関数など) は、POSIX 1003.1c 規格でのセマンティクスが Solaris オペレーティング環境 2.4 リリースでのセマンティクスと異なっています (後者は、より前の POSIX 草稿に基づいています)。関数の定義はコンパイル時に選択します。パラメタと戻り値の相違点については、『man pages section 3』を参照してください。

表 7-1 POSIX と Solaris でセマンティクスの異なる関数

sigwait(2)

asctime_r(3C)

ctime_r(3C)

getlogin_r(3C)

ftrylockfile(3S) - 新規

getgrgid_r(3C)

getgrnam_r(3C)

getpwuid_r(3C)

getpwnam_r(3C)

ttyname_r(3C)

readdir_r(3C)

 

Solaris の fork(2) 関数はすべてのスレッドを複製しますが (汎用 fork 動作)、POSIX の fork(2) 関数は Solaris の fork1() 関数と同様、呼び出しスレッドのみを複製します (fork1 動作)。

alarm(2) の処理も異なります。Solaris のアラームはそのスレッドの LWP に向けられますが、POSIX のアラームはプロセス全体に向けられます (詳細は、「スレッドごとのアラーム」を参照してください)。

<thread.h> または <pthread.h> の組み込み

インクルードファイル <thread.h> は、旧リリースの Solaris オペレーティング環境と上方互換性のあるコードをコンパイルするときに使用します (-lthread ライブラリとともに使用します)。このライブラリには両方のインタフェース、すなわち Solaris セマンティクスをもつインタフェースと POSIX セマンティクスをもつインタフェースが含まれています。POSIX スレッドで thr_setconcurrency(3T) を呼び出すためには、<thread.h> を組み込む必要があります。

インクルードファイル <pthread.h> は、POSIX 1003.1c 規格で定義されているマルチスレッドインタフェースに適合するコードをコンパイルするときに使用します (-lpthread ライブラリとともに使用します)。POSIX 完全準拠を実現するには、定義フラグ _POSIX_C_SOURCE を下記のように 199506 以上の値 (long) に設定する必要があります。


cc [flags] file... -D_POSIX_C_SOURCE=N (ただし、N は 199506L)

Solaris スレッドと POSIX スレッドを同じアプリケーションの中で混用できます。それには、<thread.h><pthread.h> の両方を組み込み、-lthread-lpthread のどちらかのライブラリとリンクします。

両者を混用した場合、コンパイルで -D_REENTRANT を指定し、リンクで -lthread を指定すると、Solaris セマンティクスが支配します。逆にコンパイルで -D_POSIX_C_SOURCE を指定し、リンクで -lpthread を指定すると、POSIX セマンティクスが支配します。

_REENTRANT または _POSIX_C_SOURCE の指定

POSIX 動作を望む場合は、-D_POSIX_C_SOURCE フラグで 199506L 以上の値を指定してアプリケーションをコンパイルしてください。Solaris 動作を望む場合は、-D_REENTRANT フラグを指定してマルチスレッドプログラムをコンパイルしてください。これは、アプリケーションのすべてのモジュールに当てはまります。

混用アプリケーションの場合 (たとえば、Solaris スレッドを POSIX セマンティクスで使用する場合)、コンパイルで -D_REENTRANT フラグと -D_POSIX_PTHREAD_SEMANTICS フラグを指定します。

単一のスレッドのアプリケーションをコンパイルするときは、-D_REENTRANT-D_POSIX_C_SOURCE フラグも指定しないでください。これらのフラグを指定しなければ、errnostdio などの以前の定義がすべてそのまま効力を持ちます。


注 -

スレッドライブラリ (libthread.so.1 または libpthread.so.1) にリンクされておらず、-D_REENTRANT フラグが指定されていない、シングルスレッドのアプリケーションをコンパイルしてください。これによって、putc(3s) などのマクロが再入可能な関数呼び出しに変換されるときに生じる性能の低下が少なくなります。


要約すると、-D_POSIX_C_SOURCE が指定された POSIX アプリケーションは、表 7-1 に記載されているルーチンに関して、POSIX 1003.1c セマンティクスを持ちます。-D_REENTRANT のみが指定されたアプリケーションは、これらのルーチンに関して Solaris セマンティクスを持ちます。また、-D_POSIX_PTHREAD_SEMANTICS が指定された Solaris アプリケーションは、これらのルーチンに関して POSIX セマンティクスを持ちますが、Solaris スレッドインタフェースを使用することもできます。

-D_POSIX_C_SOURCE-D_REENTRANT の両方が指定されたアプリケーションは、POSIX セマンティクスを持ちます。

libthread または libpthread とのリンク

POSIX スレッドの動作を望む場合は、-lpthread ライブラリをロードしてください。Solaris スレッドの動作を望む場合は、-lthread ライブラリをロードします。POSIX のプログラマでも、-lthread を指定してリンクすることにより、Solaris での fork()fork1() の区別を維持したい場合があるでしょう。-lpthread を実行すると、fork() の動作を Solaris の fork1() 呼び出しと同じものにし、alarm(2) の動作を変更します。

libthread を使用するには -lthreadld コマンドでは -lc の前、cc コマンドでは最後にそれぞれ指定してください。

libpthread を使用するには -lpthreadld コマンドでは -lc の前、cc コマンドでは最後にそれぞれ指定してください。

スレッドを用いないプログラムをリンクするときは、-lthread-lpthread は指定しないでください。指定すると、リンク時にマルチスレッド機構が設定され、実行時に動作してしまいます。これは、シングルスレッドアプリケーションの実行速度を低下させ、リソースを浪費し、デバッグの際に誤った結果をもたらします。

図 7-1 は、コンパイルオプションを図解したものです。

図 7-1 コンパイルフローチャート

Graphic

混用の場合は、thread.hpthread.h の両方を組み込む必要があります。

リンクで -lthread-lpthread も指定しないと、libthreadlibpthread に対するすべての呼び出しが動作しなくなります。実行時ライブラリ libc には、libthreadlibpthread 内の関数の仮エントリが NULL 手続きとして数多く定義されています。正しい手続きは、libc とスレッドライブラリ (libthread または libpthread) の両方がリンクされたときに、そのスレッドライブラリによって挿入されます。

次のように正しくないフラグを指定して ld コマンドでプログラムをリンクすると、C ライブラリの動きが保証できなくなります。


.o's ... -lc -lthread ... (正しくない)

または


.o's ... -lc -lpthread ... (正しくない)

注 -

スレッドを使用する C++ プログラムでは、アプリケーションをコンパイルしてリンクするには、-lthread ではなく -mt オプションを使用します。-mt オプションは libthread とリンクし、ライブラリを適切な順序でリンクします。-lthread オプションを使用すると、プログラムがコアダンプすることがあります。


リンク時の POSIX セマフォ用 -lposix4 の指定

Solaris セマフォルーチン sema_*(3T) は、libthread ライブラリに入っています。それに対し、POSIX 1003.1c セマフォルーチン sem_*(3R) を必要とする場合は、-lposix4 ライブラリをリンクします (セマフォルーチンについては、「セマフォ」を参照してください)。

新旧のモジュールのリンク

表 7-2 に、マルチスレッド化されたオブジェクトモジュールと、以前のオブジェクトモジュールをリンクする場合の注意事項を示します。

表 7-2 コンパイル時の _REENTRANT フラグの有無

ファイルの種類 

コンパイル時の指定 

参照方法 

戻す情報 

以前のオブジェクトファイル (スレッド化されていない) と新しいオブジェクトファイル 

_REENTRANT または _POSIX_C_SOURCE フラグなし

静的記憶領域

従来の errno

新しいオブジェクトファイル 

_REENTRANT または _POSIX_C_SOURCE フラグあり

_errno (新しいバイナリエントリポイント)

スレッド定義の errno のアドレス

libnsl [TLI の広域エラー変数を得るために tiuser.h を組み込む必要があります。 ] の TLI を使用するプログラム

_REENTRANT または _POSIX_C_SOURCE フラグあり (必須)

_t_errno (新しいエントリポイント )

スレッド定義の t_errno のアドレス

代替の 1 レベル libthread ライブラリのリンク

標準の Solaris スレッドの実装は、おそらくより少ない軽量プロセス(LWP)にユーザレベルのスレッドが多重化された、2 レベルスレッディングモデル上に構築されます。LWP は、オペレーティングシステムによってプロセッサに振り分けられる、基本実行ユニットです。このメカニズムは、アプリケーションを 1 レベルのセマンティクスで書くためにスレッドを 1 対 1 で LWP に関連付ける(THR_BOUND および PTHREAD_SCOPE_SYSTEM フラグ)標準実装で提供されます。

Solaris 8 オペレーティング環境では、ユーザレベルスレッドが 1 対 1 で LWP に関連付けられる 1 レベルモデルの代替スレッド実装を提供します。この実装は、標準実装よりもシンプルで、いくつかのマルチスレッド化アプリケーションにとっては便利です。これは、POSIX スレッドおよび Solaris スレッドの両方に、標準実装と全く同じインタフェースを提供します。

代替の実装にリンクするには、プログラムへのリンクの際に次の実行パス -R オプションを使用します。

POSIX スレッドの場合は、以下を使用します。


cc -mt ... -lpthread ... -R /usr/lib/lwp          (32-bit)
cc -mt ... -lpthread ... -R /usr/lib/lwp/64       (64-bit)

Solaris スレッドの場合は、以下を使用します。


cc -mt ... -R /usr/lib/lwp             (32-bit)
cc -mt ... -R /usr/lib/lwp/64          (64-bit)

以前に標準スレッドライブラリにリンクされたマルチスレッド化プログラムについては、環境変数 LD_LIBRARY_PATH および LD_LIBRARY_PATH_64 を以下のように設定して、実行時プログラムを代替スレッドライブラリに結合することができます。


LD_LIBRARY_PATH=/usr/lib/lwp
LD_LIBRARY_PATH=/usr/lib/lwp:/usr/lib/lwp/64

環境変数 LD_LIBRARY_PATH が安全なプロセスに対して有効な場合は、この変数によって指定される信頼できるディレクトリのみが実行時リンカーの検索規則の増補に使用されることに注意してください。

代替の 1 レベルスレッド実装を使用する場合は、そのライブラリは非結合スレッドを使用して標準実装よりも多くの LWP を作成することがあります。LWP がオペレーティングシステムメモリーを消費するのに対し、スレッドはユーザレベルメモリーのみ消費します。このように、数千のスレッドを作成するこのライブラリにリンクされたマルチスレッド化アプリケーションは、同数の LWP を作成し、そのアプリケーションをサポートするために必要なリソースからシステムを実行することになります。

マルチスレッドプログラムのデバッグ

よく起こるミス

以下に、マルチスレッドプログラミングでよく起こるミスを示します。

次の点にも注意してください。マルチスレッドプログラムの動きは、特にバグがある場合には、同じ入力で続けて実行しても再現性がないことがよくあります。これは、スレッドのスケジューリングの順序が定まっていないからです。

一般にマルチスレッドプログラムのバグは、決定的というよりも統計的な発生傾向を示します。このため実行レベルの問題を見つけるには、ブレークポイントによるデバッグよりもトレースの方が有効です。

TNF ユーティリティによる追跡とデバッグ

TNF ユーティリティ (Solaris システムの一部) は、アプリケーションとライブラリからの性能解析情報の収集、追跡、デバッグに使用します。TNF ユーティリティは、カーネルおよび複数のユーザプロセスとスレッドからの追跡情報を集約するので、マルチスレッドコードに特に有用です。

TNF ユーティリティを使用すると、マルチスレッドプログラムの追跡とデバッグが容易になります。

truss(1) の使用

システムコールとシグナルの追跡については、truss(1) を参照してください。

adb(1) の使用

マルチスレッドプログラム内ですべてのスレッドを結合するときは、スレッドと LWP とは同義になります。その場合は、マルチスレッドプログラミングをサポートする以下の adb コマンドを用いて、各スレッドにアクセスできます。

表 7-3 マルチスレッド対応の adb コマンド

pid:A

pid で指定したプロセスに接続する。プロセスと、そのすべての LWP は停止する。

:R

プロセスから切り離す。プロセスと、そのすべての LWP は再開される。 

$L

(停止した) プロセス内の有効な LWP を一覧表示する。 

n:l

フォーカスを n で指定した LWP に切り替える。

$l

現在のフォーカスの LWP を表示する。 

num:i

num で指定したシグナルを無視する。

以下のコマンドは、条件付きブレークポイントを設定するためによく使用されます。

表 7-4 adb ブレークポイントの設定

[label],[count]:b [expression]

expression の評価結果が 0 のときにブレークポイントにヒットする。

foo,ffff:b <g7-0xabcdef

g7 = 0xABCDEF (16 進数値) のときに foo で停止する。

dbx の使用

dbx ユーティリティでは、C++、ANSI C、FORTRAN のソースプログラムをデバッグしたり、実行したりできます。dbx のコマンドは、 デバッガと同じコマンドを受けつけますが、標準端末 (tty) インタフェースを使用する点が異なります。dbx とデバッガのどちらも、現在はマルチスレッドプログラムのデバッグをサポートしています。dbx とデバッガの詳細は、dbx(1) のマニュアルページおよび『Sun WorkShop 入門』 を参照してください。

以下に示す表 7-5 にある dbx のオプションは、すべてマルチスレッドアプリケーションをサポートできます。

表 7-5 dbx のマルチスレッドプログラム用オプション

オプション 

意味 

cont at line [sig signo id]

line で指定した行から signo で指定したシグナルで実行を再開する。id は実行を再開するスレッドまたは LWP を指定する (デフォルトの値は all)。

lwp

現在の LWP を表示する。指定の LWP (lwpid) に切り替える。

lwps

現在のプロセスの、すべての LWP を一覧表示する。 

next ... tid

指定のスレッドをステップ実行する。関数呼び出しをスキップするときは、その関数呼び出しの間だけ、すべての LWP の実行が暗黙のうちに再開される。実行可能でないスレッドをステップ実行できない。 

next ... lid

指定の LWP をステップ実行する。その LWP のスレッドが実行可能であることが必要。関数をスキップするとき、すべての LWP の実行が暗黙のうちに再開されることはない。  

step... tid

指定のスレッドをステップ実行する。関数呼び出しをスキップするときは、その関数呼び出しの間だけ、すべての LWP の実行が暗黙のうちに再開される。実行可能でないスレッドをステップ実行できない。 

step... lid

指定の LWP をステップ実行する。関数をスキップするとき、すべての LWP の実行が暗黙のうちに再開されることはない。  

stepi... lid

指定の LWP 

stepi... tid

指定のスレッドが実行可能である LWP 

thread

現在のスレッドを表示する。指定のスレッド (tid) に切り替える。以下の tid のデフォルト値は現在のスレッド

thread -info [ tid ]

指定のスレッドの全情報を表示する。 

thread -locks [ tid ]

指定のスレッドが保持しているロックを一覧表示する。 

thread -suspend [ tid ]

指定のスレッドを停止状態にする。  

thread -continue [ tid ]

指定のスレッドの停止状態を解除する。 

thread -hide [ tid ]

指定のスレッド (または現在のスレッド) を見えなくする。このスレッドは、threads オプションのリストには表示されない。

thread -unhide [ tid ]

指定のスレッド (または現在のスレッド) の隠蔽を解除する。 

allthread-unhide

全スレッドの隠蔽を解除する。  

threads

全スレッドを一覧表示する。  

threads-all

通常は表示されないスレッド (ゾンビ) を表示する。  

all|filterthreads-mode

threads オプションのスレッド一覧表示にフィルタをかけるかどうかを指定する。

auto|manualthreads-mode

スレッドリストの自動更新機能を有効にする。  

threads-mode

現在のモードをエコーする。以前の任意の書式に続けてスレッドまたは LWP の ID を指定すれば、指定のエンティティのトレースバックを得ることができる。