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は複数のスレッドでタイミングを同期させるために使用される。



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