2014年4月30日水曜日

pthreadの条件変数

今日は決算報告書を 作成して 顧問の税理士さんに作成してもらって確認したあと、銀行、役所、税務署を 徘徊して 廻って国税(法人税、復興特別法人税、消費税)と県税、市税を納付してきた。
今期は若干の設備投資が発生したため、利益は少なかった。 というより、前の期での設備更新を先延ばしにしたためその期は利益が出て、逆に今期に更新分が偏ってしまった。
設備投資は計画的に行わなければ資金の運用にも、生産性にも問題が生じてしまう。 しっかりやらないと。

夕方には帰社できたが取引先は休みに入っているので、自社のプロジェクトに時間を割くことにした。

softies projectとは別に、Unix上で動作する T-Kernelもどき(サブセット)を作ることにする。
T-Kernelを扱う業務では簡単なモックを作ってテストしているが、それとは別にシミュレータも作りたくなった。
既にシミュレータはあるはずなので新たに作るのは時間の無駄になるのだが、今回の目的はシミュレーションというよりも T-Kernelの仕様を理解することにある。


主成功シナリヲ

まず、1st iterationでは、下記の関数に対して主成功シナリヲ(最も基本的な使い方)だけを実装して単体テストしてみると、それなりにうまくいった。

tk_cre_mtx  tk_del_tsk  tk_ext_tsk  tk_ref_mtx tk_slp_tsk
tk_cre_tsk  tk_dly_tsk  tk_get_tid  tk_ref_tsk tk_sta_tsk
tk_del_mtx  tk_exd_tsk  tk_loc_mtx  tk_rel_wai tk_unl_mtx
taskは pthreadに、mutexは pthread_mutexに単純に対応させた。

代替シナリヲ

2st iterationでは、代替シナリヲ(主成功シナリヲではない別の使い方)を実装して単体テストしてみる。

tk_dly_tskを使用しているテストが通らなくなった。たとえば tk_dly_tsk(2000U)としてあるので 2秒経過後に抜けてきて欲しいのだが、10ミリ秒そこそこで抜けてきてしまう。
10ミリ秒というのは単体テストのフレームワークのオーバヘッドも含んでいるので、もう少し短い時間かも知れない。
いづれにしても期待する動作になってない。

tk_dly_tskの主成功シナリヲでは、引数で指定した時間を経過した後関数から復帰するだけだったので、単純な usleep(3C)で遅延を実装していた。

tk_dly_tskの代替シナリヲでは、他タスクから tk_rel_waiを呼ばれると、SUSPEND状態が解除されてエラーコード E_RLWAIを返さなければならない。
SUSPEND状態を抜けるには他にも tk_ter_tskもあるし、tk_slp_tskで SUSPENDして tk_wup_tskで解除というのもある。

このように待ち合わせと解除の方法が多岐にわたるため、何の要因により SUSPENDを解除しようとしているのかを記憶しなければならない。

usleepではシグナルを使うことにより中断させることはできるが、複数のタスクから条件変数の操作を安全に行うことはできない。
そこで、mutexおよび条件変数を使用することにした。


バグの原因

時間待ちを行うために pthread_cond_timedwait(3T)を使用したが、問題はそこにあった。

この関数は時間待ちをするものではなく、時刻を待つものである。
そしてその時刻とは 1970年 1月 1日 0時 0分 0秒 GMTを期限とする相対値である。 多くのマニュアルには、abstimeと書かれている。

これを見落として、2st iterationで最初に書いたコードでは単純に待ち時間を与えたものを引数に使ったものだから、2秒間待つつもりが、1970年 1月 1日 0時 0分 2秒 GMTまで待つことを指定したことになる。
1970年というのは読者の中にはまだ生まれてない方もいることだろう。そのくらい大昔である。
pthread_cont_timedwaitを呼び出しても僅かなオーバヘッドが生ずるだけで、ほとんど直ちに復帰してくるのである。

バグの影響

それでも組み込みの開発でありがちな、時計が設定されていない状態では偶然それっぽい時間になるかもしれなかった。
そんな出鱈目な状態でテストをパスさせてしまうと、市場で不具合になることだろう。
シミュレーションでよかった。

そうなると、過去に開発したものが怪しくなってきた。

幸い、請け負ったものの中には pthread_cond_timedwait(3T)を使ったものが無いことが確認できた。

softies projectでは、lwd_lang_Objectクラスの waitAsMillis関数で使っている。
しかし、ちゃんと現在時刻を取得し、待ち時間を加えて使っている。
エラー処理の違いのため、lwd_lang_Objectをそのまま使用することはできないが、参考にすべきだったかも知れない。


教訓

  • ちゃんとマニュアル読め
  • 正常に動くコードを参照しろ

参考文献

  • ビル・ルイス+ダニエル・バーグ著『Pスレッドプログラミング』岩本信一訳 プレンティスホール 1999年
  • David R.Butenhof著『POSIXスレッドプログラミング』油井 尊訳 アジソン・ウェスレイ 1998年