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

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

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

マルチスレッドアプリケーションを開発するための Solaris 環境の設定

Solaris OS 上でソフトウェアを構築するには、必要なツールを開発マシンにインストールする必要があります。Solaris OS にバンドルされている標準ツールを使用する場合も、Sun Studio ツールを使用する場合も、まず開発者環境に適した Solaris ソフトウェアをインストールする必要があります。Solaris Express Developer Edition リリースを使用している場合は、開発者環境がデフォルトでインストールされます。

Solaris 10 リリースの場合、Solaris OS には、開発者向けの適切な Solaris ソフトウェアグループをインストールすると使用できる、いくつかの開発者ソフトウェアパッケージが含まれています。インストールのために、Solaris OS は、Solaris パッケージのコレクションであるソフトウェアグループに論理的に分割されています。

Solaris OS をインストールする場合は、開発者パッケージが含まれている次のいずれかのソフトウェアグループを選択する必要があります。

これらのソフトウェアグループでは、GNU C コンパイラ (gcc) や Java コンパイラ (javac) などのコンパイラを入手できます。また、GNU ソースレベルデバッガ (gdb) とモジューラデバッガ (mdb)、リンカー (ld)、ソース管理ユーティリティー (sccs)、make などのビルドユーティリティーもインストールされます。これらのファイルは、/usr/css および /usr/sfw にインストールされます。


注 –

このマニュアルでは、Sun Studio C コンパイラを使用したマルチスレッドアプリケーションの構築について説明します。Sun Studio ソフトウェアは、Solaris Express Developer Edition リリースに含まれています。Solaris 10 リリースを使用している場合は、http://developers.sun.com/sunstudio/ の Web サイトから Sun Studio ソフトウェアをダウンロードできます。


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

この節では、Sun Studio C コンパイラを使用してマルチスレッドプログラムをコンパイルする方法について説明します。 Sun Studio C コンパイラは並列プログラミング向けに最適化されており、ほかの C コンパイラにない多くの機能を備えています。C コンパイラの詳細については、『Sun Studio 12: C ユーザーズガイド』を参照してください。

コンパイルの準備

アプリケーションは、Solaris スレッドの場合は <thread.h> を、POSIX スレッドの場合は <pthread.h> をインクルードする必要があります。使用している API に対応するファイルか、またはアプリケーションが両方のスレッドの API を使用している場合は両方のファイルをインクルードするようにしてください。詳細は、pthread.h(3HEAD) のマニュアルページを参照してください。アプリケーションはまた、<errno.h><limits.h><signal.h><unistd.h> ファイルもインクルードする必要があります。

スレッドの選択 — Solaris または POSIX

Solaris での pthread の実装は、Solaris スレッドと完全に互換性があります。同じアプリケーションで Solaris スレッドと pthread の両方を使用できます。各スレッド実装の相違点については、pthreads(5) のマニュアルページを参照してください。また、相違点については、このマニュアルの第 6 章Solaris スレッドを使ったプログラミングも参照してください。

スレッドの相違点の 1 つに、fork 関数の動作があります。

Solaris 9 リリースでは、fork() 関数の動作は、アプリケーションが POSIX スレッドライブラリにリンクされているかどうかによって決定されていました。-lthread (Solaris スレッド) にリンクされ、-lpthread (POSIX スレッド) にリンクされていない場合は、fork() によって、親プロセスのすべてのスレッドが子スレッド内に複製されました。アプリケーションが -lpthread にリンクされている場合は、-lthread にもリンクされかどうかには関係なく、fork()fork1() と同じであり、呼び出しスレッドのみが複製されました。

Solaris 10 リリースからは、forkall() 関数の呼び出しによって、親プロセスのすべてのスレッドが子プロセス内に複製されるようになりました。fork1() の呼び出しによって、呼び出しスレッドのみが子プロセス内に複製されます。Solaris 10 リリースでは、fork() の呼び出しは fork1() の呼び出しと等価です。つまり、呼び出しスレッドのみが子プロセス内に複製されます。これが、fork() に対して POSIX で規定されている動作です。アプリケーションで、すべての fork セマンティクスを複製する必要がある場合は、forkall() を呼び出してください。

<thread.h> または <pthread.h>

インクルードファイル <thread.h> には、Solaris スレッドの関数の宣言が含まれています。Solaris スレッドの任意の関数を呼び出すには、プログラムで <thread.h> をインクルードする必要があります。このファイルを使用すると、以前の Solaris ソフトウェアのリリースと互換性のあるコンパイルされたコードを生成できます。

インクルードファイル <pthread.h> は pthread の関数の宣言を含んでおり、プログラムで pthread を使用する場合に必要になります。

アプリケーションで <thread.h><pthread.h> の両方をインクルードすることによって、同じアプリケーション内に Solaris スレッドと POSIX スレッドを混在させることができます。その場合は、リンクおよびコンパイル時に、pthread の API でリンクするための -lpthread フラグを指定する必要があります。

-mt を使用した場合は、Solaris スレッドの API が自動的にリンクされます。-lthread を明示的に指定する代わりに、常に -mt オプションを使用してください。pthread を使用するには、リンクのコマンド行で -mt オプションと -lpthread オプションを指定します。libpthread ライブラリによって libthread へのインタフェースが提供されるため、pthread を使用する場合も libthread が引き続き必要です。

マルチスレッドプログラムのコンパイルとリンク

Sun Studio C コンパイラ (cc) には、マルチスレッドコードのコンパイルとリンクを行うための -mt オプションが用意されています。-mt オプションは、ライブラリの適切な順序でのリンクを保証するものです。

-mt オプションは、一貫性をもって使用する必要があります。-mt でのコンパイルとリンクを別のステップで行う場合は、リンクステップとコンパイルステップで -mt オプションを使用する必要があります。1 つの変換ユニットを -mt でコンパイルおよびリンクする場合は、そのプログラムのすべてのユニットを -mt でコンパイルおよびリンクする必要があります。

Solaris 10 リリースと Solaris 9 リリースのコンパイルとリンクには同じコマンドを使用できますが、次のいくつかの相違点を考慮するようにしてください。

POSIX スレッド環境でのコンパイルとリンク

アプリケーションで pthread のみを使用するか、または Solaris スレッドと pthread の両方を使用している場合は、次のコマンドを使用してコンパイルとリンクを行います。


cc -mt [ flag ... ] file... [ library... ] -lpthread 

-mt オプションが libthread ライブラリにリンクするに対して、-lpthread オプションは libpthread ライブラリにリンクします。libpthread によって libthread へのインタフェースが提供されるため、pthread を使用する場合は両方のフラグが必要です。

-mt オプションは、コマンド行の任意の場所に指定できます。-lpthread オプションは、ユーザーライブラリのあとに指定するようにします。-mt-lpthread の相対位置は関係ありません。

たとえば、次の行はすべて等価です。

cc -mt -o myprog f1.o f2.o   -lmylib -lpthread
cc     -o myprog f1.o f2.o -mt -lmylib -lpthread
cc     -o myprog f1.o f2.o -lmylib -mt -lpthread
cc     -o myprog f1.o f2.o -lmylib -lpthread -mt

cc コマンドのオプションの詳細については、Sun Studio cc(1) のマニュアルページおよび『Sun Studio 12: C ユーザーズガイド』を参照してください。

Solaris スレッド環境でのコンパイルとリンク

Solaris スレッド環境では、アプリケーションのコンパイルとリンク時に、次のオプションを指定します。

アプリケーションで Solaris スレッドのみを使用している場合は、次のコマンドを使用してコンパイルとリンクを行います。


cc -mt [ flag ... ] file... [ library... ]

-mt オプションは、libthread ライブラリにリンクします。

cc コマンドのオプションの詳細については、Sun Studio cc(1) のマニュアルページおよび『Sun Studio 12: C ユーザーズガイド』を参照してください。

混在スレッド環境でのコンパイルとリンク

アプリケーションで pthread と Solaris スレッドの両方の関数を使用している場合は、pthread のみのコンパイルに使用したものと同じコマンドでコンパイルとリンクを行うことができます。


cc -mt [ flag ... ] file... [ library... ] -lpthread 

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

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

Solaris セマフォールーチン sema_*(3C) は、標準 C ライブラリに入っています。これに対して、「セマフォーによる同期」で説明されているように、標準 sem_*(3RT) POSIX セマフォールーチンを取得するには、リンク時に -lrt ライブラリを指定します。

代替スレッドライブラリ

Solaris 8 リリースでは、/usr/lib/lwp (32 ビット) および /usr/lib/lwp/64 (64 ビット) ディレクトリに、代替スレッドライブラリが実装されました。Solaris 9 リリースでは、このライブラリが /usr/lib および /usr/lib/64 ディレクトリに標準スレッドとして実装されています。Solaris 10 リリース以降では、すべてのスレッド機能が libc に移動し、独立したスレッドライブラリは不要になりました。/usr/lib/lwp ディレクトリは、Solaris 8 アプリケーションの互換性のために維持されています。

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

ここでは、マルチスレッドプログラミングのバグの原因となる特性について説明します。また、プログラムのデバッグに役立つユーティリティーについても説明します。

マルチスレッドプログラムでよく起こるミス

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

マルチスレッドプログラムの動作は、特にバグがある場合には、同じ入力で続けて実行しても再現性がないことがよくあります。この動作は、スレッドのスケジューリングの順序が異なる場合に発生します。

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

DTrace による追跡とデバッグ

DTrace は、Solaris OS に組み込まれている総合的な動的追跡ツールです。DTrace の機能を利用して、マルチスレッドプログラムの動作を検査できます。DTrace は、実行中のプログラムに、指定した実行パス内の場所でデータを収集するためのプローブを挿入します。収集されたデータを検証して問題の領域を特定することができます。DTrace の使用の詳細は、『Solaris 動的トレースガイド』および『DTrace ユーザーガイド』を参照してください。

Sun Developers Network の Web サイトには、DTrace Quick Reference Guide など、DTrace に関するいくつかの記事が含まれています。

Performance Analyzer によるプロファイリング

Sun Studio ソフトウェアに含まれている Performance Analyzer ツールを使用すると、マルチスレッドプログラムやシングルスレッドプログラムの広範囲なプロファイリングを行うことができます。このツールでは、指定した任意の時点でのスレッドの動作を詳細に表示できます。詳細は、Sun Studio Web ページおよびSun Studio Information Center を参照してください。

Thread Analyzer を使用したデータの競合およびデッドロックの検出

Sun Studio ソフトウェアには、Thread Analyzer と呼ばれるツールが含まれています。このツールを使用すると、マルチスレッドプログラムの実行を解析できます。このツールでは、Pthread の API、Solaris スレッドの API、OpenMP 指令、Sun 並列指令、Cray® 並列指令、またはこれらのテクノロジの混在を使用して記述されたコードに含まれているデータの競合やデッドロックなどのマルチスレッドプログラミングのエラーを検出できます。

『Sun Studio 12: スレッドアナライザユーザーズガイド』を参照してください。

dbx の使用

dbx ユーティリティーは、http://developers.sun.com/sunstudio/ から入手可能な Sun Studio 開発者ツールに含まれているデバッガです。Sun Studio dbx コマンド行デバッガでは、C、C++、および Fortran で記述されたソースプログラムをデバッグしたり実行したりできます。dbx を使用するには、まず端末ウィンドウで起動し、dbx コマンドを使用してプログラムを対話的にデバッグします。グラフィカルインタフェースの方が望ましい場合は、Sun Studio IDE (統合開発環境) のデバッグウィンドウで dbx の同じ機能を使用できます。dbx の起動方法については、dbx(1) のマニュアルページを参照してください。『Sun Studio 12: dbx コマンドによるデバッグ』Sun Studio 12: Debugging a Program With dbxのマニュアルを参照してください。Sun Studio IDE のデバッグ機能は、IDE のオンラインヘルプで説明されています。

マルチスレッドプログラムのデバッグの詳細については、『Sun Studio 12: dbx コマンドによるデバッグ』の第 11 章「マルチスレッドアプリケーションのデバッグ」を参照してください。dbx デバッガは、『Sun Studio 12: dbx コマンドによるデバッグ』の付録 B「イベント管理」で説明されているスレッドイベントのイベントハンドラを操作するためのコマンドを提供します。

Table 8–1 に記載されているすべての 表 8–1 オプションは、マルチスレッドアプリケーションをサポートします。

表 8–1 dbx のマルチスレッドプログラム用オプション

オプション 

動作 

cont at line [-sig signo id]

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

lwp [lwpid]

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

lwps

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

next ... tid

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

next ... lwpid

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

step... tid

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

step... lwpid

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

stepi... lwpid

指定の LWP で機械命令をステップ実行する (呼び出しにステップインする)。 

stepi... tid

指定のスレッドが実行可能である LWP で機械命令をステップ実行する。 

thread [ tid ]

現在のスレッドを表示するか、またはスレッド tid に切り替える。以降のすべての変型で、tid を省略すると現在のスレッドになる。

thread -info [ tid ]

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

thread -blocks [ tid ]

指定されたスレッドが保持しているロックで、ほかのスレッドをブロックしているすべてのロックを一覧表示する。 

thread -suspend [ tid ]

指定のスレッドを停止状態にして、実行されないようにする。停止状態のスレッドの threads リストには「S」が表示される。

thread -resume [ tid ]

指定のスレッドの停止状態を解除して、実行が再開されるようにする。 

thread -hide [ tid ]

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

thread -unhide [ tid ]

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

thread -unhide all

全スレッドの非表示を解除する。

threads

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

threads -all

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

threads -mode all|filter

threads オプションのスレッド一覧表示にフィルタをかけるかどうかを指定する。フィルタが有効な場合、thread -hide コマンドで非表示になっているスレッドは表示されない。

threads -mode auto|manual

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

threads -mode

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

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

Dtrace、Performance Analyzer、Thread Analyzer、および dbx は比較的最近のツールですが、アプリケーションやライブラリからの性能解析情報の収集、追跡、デバッグに以前の TNF ユーティリティーを引き続き使用することもできます。TNF ユーティリティーは、カーネルおよび複数のユーザープロセスとスレッドからの追跡情報を集約します。TNF ユーティリティーは従来から Solaris ソフトウェアに付属しています。これらのユーティリティーについては、tracing(3TNF) のマニュアルページを参照してください。

truss の使用

システム呼び出し、シグナル、およびユーザーレベル関数呼び出しの追跡方法については、truss(1) のマニュアルページを参照してください。

mdb の使用

mdb については、『Solaris モジューラデバッガ』を参照してください。

次の mdb コマンドを使って、マルチスレッドプログラムの LWP にアクセスできます。

$l

ターゲットがユーザープロセスである場合、代表スレッドの LWP ID を出力する。

$L

ターゲットがユーザープロセスである場合、ターゲット内にある各 LWP の LWP ID を出力する。

pid::attach

pid で指定したプロセスに接続する。

::release

以前に追加されたプロセスまたはコアファイルを解放する。このようなプロセスを継続するには prun(1) を使用し、再開するには MDB などのデバッガを適用する。

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

[ addr ] ::bp [+/-dDestT] [-c cmd] [-n count] sym ...

指定された場所にブレークポイントを設定します。

addr ::delete [ id | all]

指定された ID 番号のイベント指定子を削除します。