2013年8月12日月曜日

マルチスレッド その2 (multi thread 2)

pthreadでは、スレッドごとにデフォルトサイズのスタックが割り当てられるが、このサイズは pthread属性によって変更することができる。
デフォルトサイズはシステムで定義された値で、OSごとに経験的に決められた一般的なスレッドに充分な値になっている。

しかし、より多くのスタックを必要とする場合は大きな値にしなければならない。

また、使用するアプリケーションがスタックをあまり消費しないことが解かっていて、多くのスレッドを使用したい場合には積極的にスタックサイズを小さくしたい。
ただし、1スレッドあたりのスタックの最小値が決められていて、limits.hファイルの中にある記号名 PTHREAD_STACK_MINに定義されているのでこれを下回らないようにしなければならない。

Solaris 11の場合

Solarisのデフォルトサイズは 1Mバイトである。(OpenMPを使った場合は 32bitシステムでは 4Mバイト、64bitシステムでは 8Mバイト)

スタックサイズを指定しなければならないようなケースは多くないはずである。
スタックのサイズをオーバした場合、なにも警告することなしに隣接スレッドを破壊することが Solarisのドキュメントには明記されている。
値は試行錯誤を経て決められるようになっているようだ。

mprotectを呼び出してレッドゾーンを付加しておけば、オーバした場合にセグメント例外が発生するはずである。
(OpneMPでは-xcheck=stkovf を付けると良いと示されているが、gccを使用した場合にどうするかは不明。)


PTHREAD_STACK_MINを使用したプログラムを gcc 4.6.0でコンパイルしようとすると、limits.hをインクルードしてあるにも係わらずつぎのようなエラーが出てしまう。
error: ‘PTHREAD_STACK_MIN’ undeclared (first use in this function)


ヘッダファイルを調べると、次の条件を満たしていなければならないことが判る。
#if     defined(__EXTENSIONS__) || (_POSIX_C_SOURCE >= 199506L)



そういえば、『鎌坪商店これにて閉店。ちょわーん。』してから半年近く経った。
夏休み子供電話相談室くらいはと期待してたのだが…。

2013年8月1日木曜日

マルチスレッド (multi thread)

当プロジェクトのクラスはマルチスレッドのためのコードが含まれている。
しかし、実装が不完全で限られた使い方でしか動作は保証されていない。
取り分け、シングルトンオブジェクトの排他制御がまともに出来ていないのと、スレッドごとに異なる値を持たなければならない変数が静的変数になっていてスレッド間で共有されているのが課題。


mutexの初期化

オブジェクト固有の mutexは、そのオブジェクトを生成するときに初期化を行えばよい。

問題はシングルトンの場合だ。
static boolean initialized = false;

struct Something object;

  :

 if (!initialized) {
  /* object will be initialized. */
 }

このようなコードは、単一のスレッドから呼び出す場合にのみ動作が保証される。

複数スレッドから同時に呼ばれる可能性がある場合、initializedのチェックは役に立たない。

そこで、mutexにより排他制御を行って見よう。(pthreadの場合。)

#include 

static boolean initialized = false;
static pthread_t mutex;

struct Something object;

  :

 pthread_mutex_lock(&mutex);
 if (!initialized) {
  /* object will be initialized. */
 }
 pthread_mutex_unlock(&mutex);

この処理が呼ばれる度に mutex操作が発生するとオーバヘッドが大きくなるので、それを回避する必要がある場合には外側にもう一段 initializedのチェックを入れると良い。

いづれにしてもこの mutexは事前に初期化されていなければならない。

特定のアプリケーションであれば、mutex(または objectの)初期化が完了してから複数スレッドを起動するように設計すれば十分であるが、ライブラリやフレームワークなどのミドルウェアの場合そのような制約はあてにならない。

そのようなミドルウェアは、mutex(または objectの)初期化を何らかの初期化関数として提供することになるが、仮にアプリケーションが初期化関数を呼んだ後にしかスレッドを起動しなかったとしてもまだ不十分である。

アプリケーションだけでは初期化の順序を守れないケースがあるからである。


2011年のことだったと思う。開発していた組み込み製品のデバッグ担当者から彼らの上司に『main関数にブレークポイントを設定したのに効きません。』とか『main関数よりも前に関数が呼ばれます。』という電話連絡が入った。

組み込みの開発では言語 Cを使用することが多いが、そのプロジェクトは C++が使われていたのだ。

Cでは、静的変数や外部変数を初期化するときに、main関数を呼ぶ startupルーチンの中から 変数領域の初期化が行われる。(bss領域はゼロフィルされ、data領域は初期値がコピーされる。)

ユーザのコードが mainよりも前に走り始めることは無い。
しかし、C++では定数で初期化するだけでなく、コンストラクタを起動したり、関数の結果を初期値に出来る。
#include 
#include 

std::string message("Hello, world.");

int len = message.length();

int main() {
        std::cout << message << std::endl;
        std::cout << len << std::endl;
        return 0;
}
C++のときだけではない。gccの拡張機能である constructor属性を関数に指定すると、main関数よりも前に実行される。
アプリケーションが main関数よりも前に何もしないつもりでも、使用しているライブラリが何かスレッドを起こして、なにかするかも知れない。

pthread_once

pthreadの場合、pthread_onceを使えば、そのプロセスでただ一度だけ処理を実行することが保証される。
mutexの初期化を気にせずにシングルトンを実現できる。
#include 

struct Something object;

void initializer() {
 /* object will be initialized. */
}

 :

 pthread_once(initializer)
 /* use object */

これで簡単確実にオブジェクトを初期化することができるようになった。

PTHREAD_MUTEX_INITIALIZERによる mutexの初期化

多くの場合のオブジェクトの初期化は pthread_onceでうまくいくが、pthread_onceでロックしてしまう場合がある。
softiesプロジェクトでは、Objectクラスはは Classクラスのインスタンスを持っていてこれは Objectを使用する前に初期化されなければならない。
その Classクラスは Objectクラスのサブクラスであり、Classクラスを初期化する場合にはそれに先立って Objectクラスを初期化しなければならない。
このように循環している場合、pthread_onceではロックするのである。
勿論、Object用の初期化関数と Class用の初期化関数は分けてあり、pthread_onceは各々別々に呼び出されるが、その中で相手側を呼び出すことになる。 (アプリケーションから明示的にフレームワーク、ライブラリの初期化関数を呼び出すようにしていないため、順序固定で初期化されるようなコーディングは行っていない。)
Objectの pthread_onceで指定された初期化関数の中から、Classの pthread_onceで指定された初期化関数を呼ぶところまでは何の問題もない。 (順序が逆になってもそうだ。)
ところが、その Classの初期化関数から再び Objectの pthread_onceを呼び出すと pthread_onceの呼び出しから復帰しなくなる。
そこで、何とかもう一度 mutexを使う方法を検討してみた。 普段、 mutexを使用するときは pthread_mutex_init関数を使用していた。そうしなければならないと思っていた。
しかし、ドキュメントをよく読むと、PTHREAD_MUTEX_INITIALIZERという初期値があるではないか。
つぎのように定数として mutexを初期化できる。
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
これなら、main関数よりも前に startupルーチン関連以外の処理が走るよりも前に mutexが使えるようになる為、最初のような initializer変数と組み合わせてオブジェクトの初期化を行うことができる。
Classクラスと Objectクラスのような初期化が循環していても、どちらの初期化から先に行おうとしたときでもロックすることなく処理を行うことができるようになった。
ただし、ヒープメモリから割り当てた領域に mutexを配置したり、pthread_mutexattr_tを指定した初期化したい場合には PTHREAD_MUTEX_INITIALIZERは使えない。
数ヶ月前から、電車のディスプレイに中日新聞ニュースが流れないことがしばしばあり、折角μチケット買ったのに残念な思いをする。

2013年6月30日日曜日

Objectクラスと同期プリミティブ その3

条件変数 (condition variable)

あるスレッドで変数を更新し、それを別のスレッドで待って処理を行うために Objectクラスのモニタ機構を用いることができる。
このモニタによって更新の通知と待ち合わせが行われる変数を条件変数と呼ぶ。

変数の更新を待つスレッドはつぎのように記述される。
 Object_lock(object);
 Object_wait(object, 0L);
 // objectに関連付けられた変数を読み出す
 Object_unlock(object);

Object_lockによりロックが行われると Object_waitにより更新されたことの通知を待つ。
この間一時的にロックは自動解除される。そうしないと、更新する側もロックを取得しようとするため、更新動作に入れなくなる。
重要なのは、再び waitを行っていたスレッドがロックを獲得した状態で Object_waitから復帰するということである。
Object_waitの 2つ目の引数は待ち時間の上限をミリ秒単位の long値で指定する。 0Lを指定した場合は通知が行われるまで無限に待ち続ける。
条件変数は任意の変数で構わないが、objectが Objectのサブクラスであれば、条件変数はそのクラスのメンバとするのが良い。
変数を読み書きする処理をそのクラスの関数にする事で処理の詳細を隠蔽し、併せてその関数の中でロックも行うようにすることができる。
ロック操作を利用者側に任せるのはロック漏れの原因となる。
変数を読み出している最中に更なる変数の更新が行われないようオブジェクトをロックしなければならない。

変数を更新するスレッドはつぎのように記述される。
 Object_lock(object);
 // objectに関連付けられた変数に書き込む
 Object_notify(object);
 Object_unlock(object);

Object_lockと Object_unlockで囲まれた中で変数を更新し、Object_notifyで objectで何かが起こることを待っているスレッドを再開させる。
最後に Object_unlockでロックを解放する。
Object_lockを行おうとしたときに、他のスレッドが変数に書き込み中だったり、読み出し中のものがあればそれらが完了するまで待たされる。
objectを待っているスレッドが複数あるとき、Object_notifyはその中の 1個のスレッドだけを再開させる。
どのスレッドが再開されるかは処理系の実装依存である。最初に待ち状態に入ったものが解除されることを期待するようなコードを書いてはならない。
objectを待っている複数のスレッド全てを再開させるには Object_notifyの変わりに Object_notifyAllを使用する。

なお、softiesプロジェクトでは Object_lockは必ずロックを獲得した状態で復帰する。
ロックが獲得できるかどうか試してみる tryLockの仕組みは提供していない。
他のスレッドから割り込まれてキャンセルされた場合例外をスローすることを検討されているが、現在のところ例外機構は実験段階であり、キャンセルは行われない。



アンタって少な目。 半分の整髪。

2013年5月23日木曜日

Objectクラスと同期プリミティブ その2

オブジェクトのロック

オブジェクトクラスは、それ自身をロックすることができる。

下記のように lock, unlockで囲むことにより、そのオブジェクトをロックすることができる。
Object_lock(object);

// 相互排他が必要な処理

Object_unlock(object);

オブジェクトがロックされている部分にはは唯一つのスレッドしか入ることができない。
1つのスレッドがオブジェクトをロックしている間に別のスレッドが lockを行おうとすると、そのスレッドは待ち状態になる。
ロックしているスレッドが unlockを実行することにより、待ち状態になっていたスレッドがロックを獲得することができる。
これにより複数のスレッドが同時にオブジェクトにアクセスすることによる競合を防止(相互排他)することができる。

ロック待ち状態のスレッドが複数個あった場合、ロックを獲得していたスレッドがロックを解放したことによりロックを獲得するするスレッドがどれになるかは不定である。(待ち状態に入った順にロックを獲得するとは限らない)
ロックを解放したスレッドが直ちにロック待ちに入るような場合、いつも特定のスレッドだけがロックを取得し、いつまでたっても獲得できないスレッドが生じる可能性があることに留意しなければならない。

複数範囲の相互排他

ロックは1つの箇所だけではなく、1つのオブジェクトに対して複数個所で相互排他を行うこともできる。
たとえば Objectクラスのサブクラスである Dataクラスが、次のように read関数と write関数に相互排他を行った場合、readと writeが複数スレッドで同時に実行することはない。
void Data_read(Data this) {
 Data_lock(this);
 // read処理
 Data_unlock(this);
}

void Data_write(Data this) {
 Data_lock(this);
 // write処理
 Data_unlock(this);
}


複数オブジェクトの間の関係

同じ read, write関数であっても、Dataオブジェクトが異なるものの間では排他は発生しない。
たとえば、おなじ Dataクラスの 2個のオブジェクト data1, data2があったとして、data1が read中に別スレッドで data2が writeするようなことはできる。

他の資源の排他

オブジェクトと任意の資源を組み合わせることにより、ロックしたデータ以外の資源の排他制御も行うことができる。
たとえば次の例は、char配列 arrayは自身をロックする仕組みを持たないが、lockObjectと組み合わせることにより相互排他を行うことができる。
arrayにアクセスするときは lockObjectのロックを取得し、アクセスが終わったときにロックを解放すれば良い。
struct Array {
 Object lockObject;
 char array[128];
};


注意事項

注意が必要なのは、unlockを確実の行うのはプログラマの責任である。
lockされている期間に何の配慮も無く例外を送出したり longjmpを行った場合 unlockされなくなり、閉塞状態に陥る。
また、Objectクラスの初期化が完了するまでは排他制御は保証されない。これは、コンストラクタの中で自オブジェクトをコールバック登録することなどにより、コンストラクタ完了前にコールバックされるような場合に問題が発生する。



最近、開発室の床に転がって眠ってしまい、気づくと朝になってることがある。
倉庫から梱包用エアクッションを持ってきて枕代わりにしていたが、先週日本製枕を買ってきた。

2013年5月16日木曜日

Objectクラスと同期プリミティブ その1

softiesプロジェクトのクラスの基底クラスである Objectクラスについて説明する。
Javaのようにすべてのクラスの基底とするかは未決定であるが、同期プリミティブや等価性の判定の仕組みなど、オブジェクトが持っていると都合のよい機能が提供されることになる。

機能一覧

つぎに示すのはインターフェースである。
インターフェースの構造についてはインスタンス関数のテーブルを参照。
typedef struct interface_lwd_lang_Object {
        lwd_lang_Class class_instance;
        void* (*interface_of)(char* name);

        void (*finalize)(lwd_lang_Object this);
        lwd_lang_Class (*getClass)(lwd_lang_Object this);
        int (*hashCode)(lwd_lang_Object this);
        boolean (*equals)(lwd_lang_Object this, lwd_lang_Object object);
        void (*lock)(lwd_lang_Object this);
        void (*unlock)(lwd_lang_Object this);
        void (*wait)(lwd_lang_Object this, long long timeout);
        void (*notify)(lwd_lang_Object this);
        void (*notifyAll)(lwd_lang_Object this);
        lwd_lang_String (*toString)(lwd_lang_Object this);
}* interface_lwd_lang_Object;


幾つか Javaのそれを真似ている。


finalizeはインスタンスを破棄するときに呼ばれる。
Javaではただ呼ばれるだけで、実際のインスタンスのメモリは別途コレクタにより回収される。
softiesでは、finalizeの中で自オブジェクトおよび自オブジェクトとライフサイクルをともにするオブジェクトのメモリを解放しなけれならない。


hashCodeと equalsは同価性の判断に使用される。
ただし、Objectクラスでは equalsは同一性の評価を行う。
同一性は2つのインスタンスのアドレスが等しいかどうかを表す。
同価性は、2つのインスタンスのアドレスにかかわらず、値が等しいかどうかを表す。
Objectのサブクラスは hashCodeと equalsをオーバライドすることができる。
どちらか一方をオーバライドした場合はもう一方もオーバライドする必要がある事と、equalsが成立する場合の hashCodeは等しくなければならない事は Javaと同じ。


lock, unlock, wait, notify, notifyAllは同期プリミティブである。これらを実現するために、pthreadの mutexと条件変数が使用している。
Cでは synchronizedな関数やブロックを記述することができないため、lockと unlockで代用する。分岐や大域脱出などにより unlock漏れが無いようにするのはプログラマの責任である。


wait, notify, notifyAllはいづれも lockと unlockでくくられていなければならない。
notify、notifyAllにより同一のモニタで waitしているスレッドの実行を再開させる。両者の違いは同一のモニタで waitしているスレッドが複数あるときに、notifyは waitしているもスレッドうちの1つだけを再開させるのに対し、notifyAllは全てを再開させることである。
notifyでどのスレッドが再開するかは特定することはできない。waitした順とは限らないし、いつも同じスレッドに偏るかも知れない。
notifyはスレッドプールのように要求を処理するスレッドを割り当てるために使用される。
notifyAllは複数のスレッドでタイミングを同期させるために使用される。



実質的増税が続くなか、関税は下がる傾向にある。 関税も上げよ。