15.3.3 Condition オブジェクト

条件変数(condition variable) は常にある種のロックに関連付けられています; 条件変数に関連付けるロックは明示的に引き渡したり、デフォルトで生成させたり できます。 (複数の条件変数で同じロックを共有するような場合には、引渡し による関連付けが便利です。)

条件変数には、acquire() メソッドおよびrelease() があり、関連付けされているロックの対応するメソッドを呼び出すように なっています。また、 wait(), notify(), notifyAll() といったメソッドがあります。これら三つの メソッドを呼び出せるのは、呼び出し手のスレッドがロックを獲得している 時だけです。

wait()メソッドは現在のスレッドのロックを解放し、他のスレッドが 同じ条件変数に対してnotify()またはnotifyAll() を呼び 出して現在のスレッドを起こすまでブロックします。一度起こされると、 再度ロックを獲得して処理を戻します。wait() にはタイムアウトも 設定できます。

notify()メソッドは条件変数待ちのスレッドを1つ起こします。 notifyAll()メソッドは条件変数待ちの全てのスレッドを起こします。

注意: notify()notifyAll()はロックを解放しません; 従って、スレッドが起こされたとき、wait() の呼び出しは即座に 処理を戻すわけではなく、notify() またはnotifyAll() を呼び出したスレッドが最終的にロックの所有権を放棄したときに初めて 処理を返すのです。

豆知識: 条件変数を使う典型的なプログラミングスタイルでは、 何らかの共有された状態変数へのアクセスを同期させるためにロックを使います; 状態変数が特定の状態に変化したことを知りたいスレッドは、自分の望む 状態になるまで繰り返し wait() を呼び出します。その一方で、 状態変更を行うスレッドは、前者のスレッドが待ち望んでいる状態で あるかもしれないような状態へ変更を行ったときに notify()notifyAll() を呼び出します。例えば、以下のコードは無制限の バッファ容量のときの一般的な生産者-消費者問題です:

# Consume one item
cv.acquire()
while not an_item_is_available():
    cv.wait()
get_an_available_item()
cv.release()

# Produce one item
cv.acquire()
make_an_item_available()
cv.notify()
cv.release()

notify()notifyAll() のどちらを使うかは、 その状態の変化に興味を持っている待ちスレッドが一つだけなのか、あるいは 複数なのかで考えます。例えば、典型的な生産者-消費者問題では、 バッファに 1 つの要素を加えた場合には消費者スレッドを 1 つしか 起こさなくてかまいません。

クラス Condition( [lock])
lock を指定して、None の値にする場合、 Lock またはRLock オブジェクトでなければなりません。 この場合、lock は根底にあるロックオブジェクトとして使われます。 それ以外の場合には新しい RLock オブジェクトを生成して 使います。

acquire( *args)
根底にあるロックを獲得します。 このメソッドは根底にあるロックの対応するメソッドを呼び出します。 そのメソッドの戻り値を返します。

release( )
根底にあるロックを解放します。 このメソッドは根底にあるロックの対応するメソッドを呼び出します。 戻り値はありません。

wait( [timeout])
通知 (notify) を受けるか、タイムアウトするまで待機します。 このメソッドを呼び出してよいのは、呼び出し手のスレッドがロックを獲得 しているときだけです。

このメソッドは根底にあるロックを解放し、他のスレッドが同じ条件変数に 対してnotify()またはnotifyAll() を呼び出して現在の スレッドを起こすか、オプションのタイムアウトが発生するまでブロック します。一度スレッドが起こされると、再度ロックを獲得して処理を戻します。

timeout引数を指定して、None以外の値にする場合、 タイムアウトを秒 (または端数秒) を表す浮動小数点数でなければなりません。

根底にあるロックがRLock である場合、release() メソッド ではロックは解放されません。というのも、ロックが再帰的に複数回獲得 されている場合には、release() によって実際にアンロックが 行われないかもしれないからです。その代わり、 ロックが再帰的に複数回 獲得されていても確実にアンロックを行えるRLock クラスの 内部インタフェースを使います。その後ロックを再獲得する時に、 もう一つの内部インタフェースを使ってロックの再帰レベルを復帰します。

notify( )
この条件変数を待っているスレッドがあれば、そのスレッドを起こします。 このメソッドを呼び出してよいのは、呼び出し手のスレッドがロックを獲得 しているときだけです。

何らかの待機中スレッドがある場合、そのスレッドの一つを起こします。 待機中のスレッドがなければ何もしません。

現在の実装では、待機中のメソッドをただ一つだけ起こします。 とはいえ、この挙動に依存するのは安全ではありません。 将来、実装の最適化によって、複数のスレッドを起こすようになるかも しれないからです。

注意: 起こされたスレッドは実際にロックを再獲得できるまでwait() 呼出しから戻りません。notify()はロックを解放しないので、 notify() 呼び出し手は明示的にロックを解放せねばなりません。

notifyAll( )
この条件を待っているすべてのスレッドを起こします。 このメソッドはnotify() のように動作しますが、 1 つではなくすべての待ちスレッドを起こします。

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。