この章では、マルチスレッドプログラムのコンパイルとデバッグについて説明します。
ヘッダファイル、定義フラグ、リンクなどについては、考慮すべきオプションが多数あります。
マルチスレッドプログラムのコンパイルとリンクには、次のものが必要です。C コンパイラ以外は、Solaris 2.x システムに付属しています。
標準 C コンパイラ
インクルードファイル
Solaris スレッドライブラリ (libthread) と POSIX スレッドライブラリ (libpthread)。セマフォ用の POSIX リアルタイムライブラリ (libposix4) も必要な場合があります。
「MT-安全」ライブラリ (libc、libm、libw、libintl、libnsl、libsocket、libmalloc、libmapmalloc など)
一部の関数 (次の表に示した関数など) は、POSIX 1003.1c 規格でのセマンティクスが Solaris 2.4 リリースでのセマンティクスと異なっています (後者は、より前の POSIX 草稿に基づいています)。関数の定義はコンパイル時に選択します。パラメタと戻り値の相違点については、『SunOS Reference Manual』の Section 3「Library Routines」を参照してください。
表 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> は、旧リリースの 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 セマンティクスが支配します。
POSIX 動作を望む場合は、-D_POSIX_C_SOURCE フラグで 199506L 以上の値を指定してアプリケーションをコンパイルしてください。Solaris 動作を望む場合は、-D_REENTRANT フラグを指定してマルチスレッドプログラムをコンパイルしてください。これは、アプリケーションのすべてのモジュールに当てはまります。
混用アプリケーションの場合 (たとえば、Solaris スレッドを POSIX セマンティクスで使用する場合)、コンパイルで -D_REENTRANT フラグと -D_POSIX_PTHREAD_SEMANTICS フラグを指定します。
単一のスレッドのアプリケーションをコンパイルするときは、-D_REENTRANT も -D_POSIX_C_SOURCE フラグも指定しないでください。これらのフラグを指定しなければ、errno、stdio などの以前の定義がすべてそのまま効力をもちます。
要約すると、-D_POSIX_C_SOURCE が指定された POSIX アプリケーションは、表 7-1 に記載されているルーチンに関して、POSIX 1003.1c セマンティクスを持ちます。-D_REENTRANT のみが指定されたアプリケーションは、これらのルーチンに関して Solaris セマンティクスを持ちます。また、-D_POSIX_PTHREAD_SEMANTICS が指定された Solaris アプリケーションは、これらのルーチンに関して POSIX セマンティクスを持ちますが、Solaris スレッドインタフェースを使用することもできます。
POSIX スレッドの動作を望む場合は、-lpthread ライブラリをロードしてください。Solaris スレッドの動作を望む場合は、-lthread ライブラリをロードします。POSIX のプログラマでも、-lthread を指定してリンクすることにより、Solaris での fork() と fork1() の区別を維持したい場合があるでしょう。-lpthread を実行すると、fork() の動作を Solaris の fork1() 呼び出しと同じものにし、alarm(2) の動作を変更します。
libthread を使用するには -lthread を ld コマンドでは -lc の前、cc コマンドでは最後にそれぞれ指定してください。
libpthread を使用するには -lpthread を ld コマンドでは -lc の前、cc コマンドでは最後にそれぞれ指定してください。
スレッドを用いないプログラムをリンクするときは、-lthread と -lpthread は指定しないでください。指定すると、リンク時にマルチスレッド機構が設定され、実行時に動作してしまいます。これは、シングルスレッドアプリケーションの実行速度を低下させ、資源を浪費し、デバッグの際に誤った結果をもたらします。
図 7-1 は、コンパイルオプションを図解したものです。
混用の場合は、thread.h と pthread.h の両方を組み込む必要があります。
リンクで -lthread も -lpthread も指定しないと、libthread と libpthread に対するすべての呼び出しが動作しなくなります。実行時ライブラリ libc には、libthread と libpthread 内の関数の仮エントリが NULL 手続きとして数多く定義されています。正しい手続きは、libc とスレッドライブラリ (libthread または libpthread) の両方がリンクされたときに、そのスレッドライブラリによって挿入されます。
次のように正しくないフラグを指定して ld コマンドでプログラムをリンクすると、C ライブラリの動きが保証できなくなります。
.o's ... -lc -lthread ... (正しくない) または .o's ... -lc -lpthread ... (正しくない)
スレッドを使用する C++ プログラムでは、アプリケーションをコンパイルしてリンクするには、-lthread ではなく -mt オプションを使用します。-mt オプションは libthread とリンクし、ライブラリを適切な順序でリンクします。-lthread オプションを使用すると、プログラムがコアダンプすることがあります。
Solaris セマフォルーチン sema_*(3T) は、libthread ライブラリに入っています。それに対し、POSIX 1003.1c セマフォルーチン sem_*(3R) を必要とする場合は、-lposix4 ライブラリをリンクします (セマフォルーチンについては、「セマフォ」を参照してください)。
表 7-2 に、マルチスレッド化されたオブジェクトモジュールと、以前のオブジェクトモジュールをリンクする場合の注意事項を示します。
表 7-2 コンパイル時の _REENTRANT フラグの有無
以下に、マルチスレッドプログラミングでよく起こるミスを示します。
呼び出し側のスタックへのポインタを新しいスレッドの引数として渡す。
2 つのスレッドが異なる順序で、同じ組の広域資源への権利を獲得しようとしてデッドロックが発生する。この場合は、一方のスレッドが最初の資源を獲得し、もう一方のスレッドが 2 番目の資源を獲得し、どちらかが資源を放棄するまで処理が進まなくなります。
すでに保持しているロックを獲得しようとする (再帰的なデッドロック)。
同期機構の安全性に見えない間隙が生じている。これは、同期機構によって保護されているプログラム内で同期機構をいったん解除し、再度獲得してから戻る関数を呼び出していることが原因です。関数の呼び出し側から見ると広域データが保護されているようでも、実際には保護されていません。
UNIX のシグナルとスレッドを組み合わせて使っている。非同期的なシグナルの処理には sigwait(2) を使用するほうがよいでしょう。
setjmp(3B) と longjmp(3B) を使用し、相互排他ロックを解放せずにロングジャンプする。
*_cond_wait(3T) または *_cond_timedwait(3T) の呼び出しから復帰した後、条件の再評価に失敗した。
デフォルトスレッドを PTHREAD_CREATE_JOINABLE として生成した場合は、その記憶領域を pthread_join(3T) で再利用しなければならないことを忘れている。なお、pthread_exit(3T) は記憶領域を解放しません。
スタックの大きさの指定が適切でないか、デフォルト以外のスタックを使用している。
次の点にも注意してください。マルチスレッドプログラムの動きは、特にバグがある場合には、同じ入力で続けて実行しても再現性がないことがよくあります。これは、スレッドのスケジューリングの順序が定まっていないからです。
一般にマルチスレッドプログラムのバグは、決定的というよりも統計的な発生傾向を示します。このため実行レベルの問題を見つけるには、ブレークポイントによるデバッグよりもトレースの方が有効です。
TNF ユーティリティ (Solaris システムの一部) は、アプリケーションとライブラリからの性能解析情報の収集、追跡、デバッグに使用します。TNF ユーティリティは、カーネルおよび複数のユーザプロセスとスレッドからの追跡情報を集約するので、マルチスレッドコードに特に有用です。
TNF ユーティリティを使用すると、マルチスレッドプログラムの追跡とデバッグが容易になります。prex(1)、tnfdump(1) などの TNF ユーティリティの使用方法の詳細は、『Programming Utilities Guide』の TNF ユーティリティの章を参照してください。
システムコールとシグナルの追跡については、『SunOS Reference Manual』の Section 1「User Commands」の truss(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 ユーティリティでは、C++、ANSI C、FORTRAN、Pascal のソースプログラムをデバッグしたり、実行したりできます。dbx のコマンドは、SPARCworksTM デバッガと同じコマンドを受けつけますが、標準端末 (tty) インタフェースを使用する点が異なります。dbx とデバッガのどちらも、現在はマルチスレッドプログラムのデバッグをサポートしています。dbx とデバッガの詳細は、SunSoft 開発者向け製品 (旧 SunPro) の dbx(1) のマニュアルページと『Debugging a Program』を参照してください。
以下に示す表 7-5 にある dbx のオプションは、すべてマルチスレッドアプリケーションをサポートできます。
表 7-5 dbx のマルチスレッドプログラム用オプション