ここでは、マルチスレッドプログラミングのバグの原因となる特性について説明します。また、プログラムのデバッグに役立つユーティリティーについても説明します。
以下に、マルチスレッドプログラミングでよく起こるミスを示します。
大域メモリー (変更が可能で、かつ共有されている状態) にアクセスするときに同期機構で保護していないために、「データの競合」が発生する。1 つのプロセス内の 2 つ以上のスレッドが同じメモリー位置に同時にアクセスし、少なくとも 1 つのスレッドがそのメモリー位置に書き込もうとすると、データの競合が発生します。スレッドが排他的ロックを使用してそのメモリーへのアクセスを制御していない場合は、アクセスの順序が決定性ではなくなるため、その順序に応じて、実行するたびに異なる計算結果が生成されることがあります。問題にはならないデータの競合 (たとえば、ビジー状態の待機にメモリーアクセスが使用されている場合) もありえますが、データの競合の多くはプログラムのバグです。データの競合の検出には、Thread Analyzer ツールが有効です。「Thread Analyzer を使用したデータの競合およびデッドロックの検出」を参照してください。
2 つのスレッドが異なる順序で、同じ組の大域リソースへの権利を獲得しようとしてデッドロックが発生する。この場合は、一方のスレッドが最初のリソースを獲得し、もう一方のスレッドが 2 番目のリソースを獲得します。どちらかがリソースを放棄するまで処理が進まなくなります。Thread Analyzer ツールは、デッドロックの検出にも有効です。「Thread Analyzer を使用したデータの競合およびデッドロックの検出」を参照してください。
同期保護において見えない間隙が生じている。この保護における間隙は、保護されているコードセグメントに、呼び出し側に復帰する前に同期機構をいったん解放し、再度獲得する関数が含まれている場合に発生します。この結果、問題が発生します。呼び出し側から見ると大域データは保護されているようでも、実際には保護されていません。
UNIX のシグナルとスレッドを組み合わせて使用しているときに、非同期的なシグナルの処理に sigwait(2) モデルを使用しない。
*_cond_wait() または *_cond_timedwait() の呼び出しから復帰した後、条件の再評価に失敗した。
デフォルトスレッドを PTHREAD_CREATE_JOINABLE として生成した場合は pthread_join(3C) で再利用しなければならないことを忘れている。pthread_exit(3C) は記憶領域を解放しません。
深い入れ子の再帰呼び出しを行なったり、大量の自動配列を使用したりする。マルチスレッドプログラムはシングルスレッドプログラムよりもスタックの大きさの制限が厳しいので、問題が発生することがあります。
マルチスレッドプログラムの動作は、特にバグがある場合には、同じ入力で続けて実行しても再現性がないことがよくあります。この動作は、スレッドのスケジューリングの順序が異なる場合に発生します。
一般にマルチスレッドプログラムのバグは、決定的というよりも統計的な発生傾向を示します。このため実行レベルの問題を見つけるには、ブレークポイントによるデバッグよりも追跡の方が有効です。
DTrace は、Solaris OS に組み込まれている総合的な動的追跡ツールです。DTrace の機能を利用して、マルチスレッドプログラムの動作を検査できます。DTrace は、実行中のプログラムに、指定した実行パス内の場所でデータを収集するためのプローブを挿入します。収集されたデータを検証して問題の領域を特定することができます。DTrace の使用の詳細は、『Solaris 動的トレースガイド』および『DTrace ユーザーガイド』を参照してください。
Sun Developers Network の Web サイトには、DTrace Quick Reference Guide など、DTrace に関するいくつかの記事が含まれています。
Sun Studio ソフトウェアに含まれている Performance Analyzer ツールを使用すると、マルチスレッドプログラムやシングルスレッドプログラムの広範囲なプロファイリングを行うことができます。このツールでは、指定した任意の時点でのスレッドの動作を詳細に表示できます。詳細は、Sun Studio Web ページおよびSun Studio Information Center を参照してください。
Sun Studio ソフトウェアには、Thread Analyzer と呼ばれるツールが含まれています。このツールを使用すると、マルチスレッドプログラムの実行を解析できます。このツールでは、Pthread の API、Solaris スレッドの API、OpenMP 指令、Sun 並列指令、Cray® 並列指令、またはこれらのテクノロジの混在を使用して記述されたコードに含まれているデータの競合やデッドロックなどのマルチスレッドプログラミングのエラーを検出できます。
『Sun Studio 12: スレッドアナライザユーザーズガイド』を参照してください。
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 を指定すれば、指定のエンティティーのトレースバックを得ることができる。 |
Dtrace、Performance Analyzer、Thread Analyzer、および dbx は比較的最近のツールですが、アプリケーションやライブラリからの性能解析情報の収集、追跡、デバッグに以前の TNF ユーティリティーを引き続き使用することもできます。TNF ユーティリティーは、カーネルおよび複数のユーザープロセスとスレッドからの追跡情報を集約します。TNF ユーティリティーは従来から Solaris ソフトウェアに付属しています。これらのユーティリティーについては、tracing(3TNF) のマニュアルページを参照してください。
システム呼び出し、シグナル、およびユーザーレベル関数呼び出しの追跡方法については、truss(1) のマニュアルページを参照してください。
mdb については、『Solaris モジューラデバッガ』を参照してください。
次の mdb コマンドを使って、マルチスレッドプログラムの LWP にアクセスできます。
ターゲットがユーザープロセスである場合、代表スレッドの LWP ID を出力する。
ターゲットがユーザープロセスである場合、ターゲット内にある各 LWP の LWP ID を出力する。
pid で指定したプロセスに接続する。
以前に追加されたプロセスまたはコアファイルを解放する。このようなプロセスを継続するには prun(1) を使用し、再開するには MDB などのデバッガを適用する。
以下のコマンドは、条件付きブレークポイントを設定するためによく使用されます。
指定された場所にブレークポイントを設定します。
指定された ID 番号のイベント指定子を削除します。