トップ «前の日記(2009-12-21) 最新 次の日記(2009-12-23)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2009-12-22

_ マルチスレッドと同期

久々にマルチスレッドで同期するプログラムを書いていたのだが、ある程度パターン化できているように思う。しかし、よりうまい方法もありそうにも思う。また、間違えている可能性もある。そこで、2つほど並べてみる。また、3番目として実際の問題についても書いておく。どのように構成すればよいかを考えてみるとなかなかおもしろい(僕はおもしろかった)ので、それについてはとりあえずどうしたかは省略するので考えると良いと思う。

以下では、3つのスレッド(実際にはより多数あると考えてよい)を使って示す。1は元の状態を利用して実行している先行スレッド、2が状態の変化A→Bを検知して自身および後続のスレッドに新たな状態を利用させるスレッド、3は2より遅れて実行されるため最初から新たな状態を利用するスレッドである。+は実行の開始地点を、*は状態変化の検知からその状態への遷移までの区間を示す。また、()はその状態の利用区間を示す。

1. ある時点で状態が変わり、その後はその状態を利用する場合。

  スレッド1
     +------(-----------------)----->
  スレッド2
                +------(***--------------)----->
  スレッド3
                   +------ (-----------------)----->

この場合は、先行する1に対しては同期を行わず、3に対しては遷移後の状態を利用させる。

Object lock;
State state; 
...
void local() {
    State localState;  // 実行中参照する状態値
    synchronized(lock) {
        if (A → B) {
            state = B;
        }
        localState = state;
    }
    ...
}

1. ある時点で状態が変わり、その後はその状態を利用するが、同時に複数の状態で実行してはならない場合。

  スレッド1
     +------(-----------------)----->
  スレッド2
                +------(*      **--------------)----->
  スレッド3
                   +------       (-----------------)----->

同時に複数の状態のスレッドが実行されてはならないため、2は先行する1の状態参照区間が終了するまで状態変更を待たなければならない。スレッド2が待ち状態に入るため、スレッド3も明示的な待ち状態を作る必要がある。

Object lock;
boolean changing;
int running;
State state; 
...
void local() throws InterruptException {
    State localState;
    synchronized(lock) {
        while (changing) {
            lock.wait();
        }
        if (A → B) {
            changing = true;
            try {
                while (running > 0) {
                    lock.wait();
                }
                state = B;
            } finally {
                changing = false;
                lock.notifyAll();
            }
        }
        localState = state;
        running++;
    }
    try {
        ...
    } finally {
        synchronized (lock) {
            running--;
            lock.notifyAll();
        }
    }
}

状態変更を検出するのは同時に1スレッドのためbooleanを利用するが、先行スレッドは複数存在するためカウンターを利用する。

2と3の停止理由は異なることから、異なるモニターを割り当てることは可能であり、かつ意味的には整理されるが、プログラムは煩雑になる。したがって単一のモニタを共用すべきと思う。

3.(ここからがおもしろい)2のプロセスが複数存在し、すべてのプロセスに同期が必要な場合。たとえばクラスタリングされたサーバで実行されているサーバアプリケーション。

いずれのプロセスが状態変化を検知するかはわからない。したがって、すべてのプロセスは対称的に動作する。

どこで何回通信を行うかと、通信を受信したスレッド(スレッド4)はどのように動作するかが問題となる。

例)
プロセスa
  スレッド1
     +------(-----------------)----->
  スレッド2
                +------(*      **--------------)----->
  スレッド3                      |
                   +------      |(-----------------)----->
プロセスb                        |
  スレッド1                      V
                  +------(-----------------)----->
             プロセスbではまだスレッド1(以前の状態)が実行中

すべてのプロセスを対称的に実装した場合、状態変更の通知のコンテンション(プロセスaからプロセスbへの通知と、プロセスbからプロセスaへの通知が同時)が発生し得ることに注意が必要。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|

ジェズイットを見習え