並列処理プログラムのデバッグも、逐次処理と同じ方法で行うことができます。
デバッガでは、スレッドの情報を見ることもできます。
終わらない並列処理の原因調査
前回の終わらないプログラムの対策について、本サイトを運営するEMCメンバーの方々にも相談し、解決のヒントを頂きました。
•マルチスレッドで動かさず、シングルスレッドで動かしてみる
このプログラムの場合、前からと後ろからの2つの関数が動きますが、どちらか片方でも処理は可能ですし、試すのも簡単で、スレッドを1つ起こすだけです。
結果は、問題なく動作しました。 おそらくミューテックス変数の使い方に誤りは無いと思います。
赤枠内を無効にしています。
次は、地道なprintf()による情報取得を行おうとしたのですが、その作業を始めた時に問題に気付きました。
EMC メンバーの助言のおかげです。
ミューテックス変数のunlock漏れ
原因はミューテックス変数の開放漏れでした。
排他処理で囲まれたループをbreakで抜ける時、ミューテックス変数は lock 状態のままでした。
上下に分かれて並列動作によりデータを加算する2つの関数がぶつかるまで、おそらく交互に動作するはずですが、片方が処理を終えてループを抜けた時、もう片方はミューテックス変数のunlock待ちで止まってしまいます。
ループを抜けたところでミューテックス変数をunlockすることにより、ブログラムは無事終了するようになりました。
2関数とも同じ対策を行いました。
break以外でループを抜けた場合には、unlockエラーになるはずですが、エラーは無視しました。
単純な不具合の発生理由
解かってみれば、はずかしい不具合であったわけですが、原因を考えてみました。
よくあることですが、思い込みによるところが大きかったと思います。
では、なぜ不具合原因を発見できたのでしょうか。
•挿入するprintf()は処理の区切りに入れようとする
『プログラムは思った通りに動くのではなく、書いた通りに動く』 の名言どおりでした。
本例のように全体を見渡せる小さなプログラムでは比較的簡単に気付きそうですが、もっと大きな、複雑なループ処理では、なかなか気付かないのかもしれません。また、今回のバグはミューテックス変数をロックしたままループから抜けてしまうバグでしたが、割り込み禁止・許可の制御でも同様な問題が発生し得るでしょう。こういった不具合を見つける手法を確立することができると良さそうです。
ところで、今回発生した停止状態はデバッガではどのように見えるのでしょうか。
デバッグモード( -g オプション付き) でコンパイルした後、gdb でプログラムを起動してみます。
プログラムが停止したあたりで、Ctrl-Cでコマンド入力状態にし、where コマンドをつかって今どこにいるのかを見てみます。
システムのどこかで停止しているようですが、どこだか判りません。
では、スレッド状態を見てみます。コマンドは、 info thread です。
見覚えのある sample1_2 という名前が2つありますので、1番と6番のスレッドでプログラムがどこにいるか、再び where コマンドで確認してみます。
thread 1 は、114行目の pthread_join 呼び出しの中にいるようです。
thread 6 は どうでしょうか。
こちらは、61行目で止まっています。
ソースを見ると、pthread_mutex_lock(&mutex_1) の場所で、ミューテックス変数がロックできない状態であることが判ります。
一方が加算処理を終えて pthread_join 待ち状態にあり、もう一方がミューテックス変数のロック処理で停止していることをみれば、おそらく先に終わった方がミューテックス変数のアンロックをしていないのではないかと疑うことになりそうです。
サンプルプログラムはとても簡単で小さなものですが、もっと大きなプログラムでループも大きければ、こんな簡単な問題でも気づきにくいかもしれません。
排他処理区間を抜けた時、確実に排他解除が行われているか、抜け道はないかを確認する必要がありそうです。
排他区間を絞り込む
さて、上の方で、ループを丸ごと排他処理することで並列区間が小さくなって性能が出ないという話をしました。
そこで、次は排他処理区間を狭くする改造を行ってみます。
さらに、複数のミューテックス変数を使ってみます。競合する変数が2つあるので、それぞれにミューテックス変数を割り当てます。
再び停止
この対策を行ったプログラムですが、再び途中で停止してしまいました。
次回は、その現象についてお話しします。
コメントをお書きください