spurious wakeup の具体例
avail
に対して待機し、avail
への通知をうけてFIFOキューからデータを取り出す。つまり条件変数avail
を、条件﹁キュー内にデータが存在する﹂まで待機するスレッド間同期機構として利用している。このとき★箇所の待機処理avail.wait(lk)
において、“条件変数への通知を行っていないにも関わらずブロックが解除されてスレッドが動きだす” ことがあり得る。
#include <deque> #include <mutex> #include <condition_variable> struct Data { /*...*/ }; std::deque<Data> queue; // FIFOキュー std::mutex mtx; // queue保護 std::condition_variable avail; // queueに有効なデータが存在? // 生産者スレッド void producer() { for (;;) { Data data = /* データ生成 */; { std::lock_guard<std::mutex> lk(mtx); queue.push_back(data); } avail.nofity_one(); // 条件変数availへ通知 } } // 消費者スレッド void consumer() { for (;;) { Data data; { std::unique_lock<std::mutex> lk(mtx); while (queue.empty()) { avail.wait(lk); // ★条件変数availへの通知を待機 } data = queue.front(); queue.pop_front(); } /* データ消費 */; } }上記コードでは、条件変数の待機処理と待機条件のチェック
queue.
empty()
がループ中に記載されている。このため、仮に spurious wakeup によりブロックが解除されても、待機条件を満足しない︵キュー内にデータが存在しない︶ことを検知し再び条件変数に対して待機する。参考のため、条件変数の誤った利用コードと spurious wakeup で生じる問題を下記に示す。
// BUG: 消費者スレッドの誤った実装 { std::unique_lock<std::mutex> lk(mtx); if (queue.empty()) { // BUG: 待機処理がループで囲われていない avail.wait(lk); // (1) spurious wakeupでブロック解除されると... } // (2) FIFOキューが空なのに取り出し操作! data = queue.front(); queue.pop_front(); }このような定型処理のために、第2引数に Predicate をとる
wait
メンバ関数オーバーロードが提供されている。
template <class Predicate> void wait(unique_lock<mutex>& lock, Predicate pred); // Effects: while (!pred()) wait(lock);明示的ループ構造が不要となり、条件変数の誤った利用リスクが無くなるため、可能な限りこちらを利用すべき。さらに
wait
メンバ関数から制御が戻る=条件変数に通知が行われた となるため、利用者からは spurious wakeup が完全に隠蔽される。
{ std::unique_lock<std::mutex> lk(mtx); // 条件「キュー内にデータが存在する」まで待機 avail.wait(lk, [&]{ return !queue.empty(); }); data = queue.front(); queue.pop_front(); }
C++11標準ライブラリ:std::condition_variable
C++11標準ライブラリで追加された条件変数オブジェクト(std::condition_variable
およびstd::condition_variable_any
)において、spurious なブロック解除&スレッド始動が起こりえると言及されている。N3337 30.5.1/p10より引用(下線部は強調)。
void wait(unique_lock
& lock);
Effects:
- Atomically calls
lock.unlock()
and blocks on*this
.- When unblocked, calls
lock.lock()
(possibly blocking on the lock), then returns.- The function will unblock when signaled by a call to
notify_one()
or a call tonotify_all()
, or spuriously.- If the function exits via an exception,
lock.lock()
shall be called prior to exiting the function scope.
Boost.Threadライブラリ:boost::condition_variable
Boost.Threadの条件変数オブジェクト(boost::condition_variable
およびboost::condition_variable_any
)も同様。
Effects:
http://www.boost.org/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable.wait
Atomically calllock.unlock()
and blocks the current thread. The thread will unblock when notified by a call tothis->notify_one()
orthis->notify_all()
, or spuriously. When the thread is unblocked (for whatever reason), the lock is reacquired by invokinglock.lock()
before the call towait
returns. The lock is also reacquired by invokinglock.lock()
if the function exits with an exception.
POSIX(pthread):pthread_cond_t
pthreadの条件変数(pthread_cond_t
型)。wikipedia:en:Spurious_wakeupによればpthreadが発祥?
When using condition variables there is always a Boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur. Since the return from pthread_cond_timedwait() or pthread_cond_wait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.
http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cond_wait.html
(The following sections are informative.)
http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_cond_signal.html
RATIONALE
An added benefit of allowing spurious wakeups is that applications are forced to code a predicate-testing-loop around the condition wait. This also makes the application tolerate superfluous condition broadcasts or signals on the same condition variable that may be coded in some other part of the application. The resulting applications are thus more robust. Therefore, IEEE Std 1003.1-2001 explicitly documents that spurious wakeups may occur.
Windows API:CONDITION_VARIABLE
Windows APIの条件変数(CONDITION_VARIABLE
型)はWindows Vistaで追加された。同APIはWindows XP以前には存在しない。
Condition variables are subject to spurious wakeups (those not associated with an explicit wake) and stolen wakeups (another thread manages to run before the woken thread). Therefore, you should recheck a predicate (typically in a while loop) after a sleep operation returns.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682052.aspx
Java:Object#wait(), Conditionインタフェース
Object
型︶をモニター機構として利用できる。また、Java1.5で追加された java.util.concurrent.locks パッケージに条件変数︵Condition
インタフェース︶が存在する。これらの待機処理においても同様の spurious wakeup が生じうる。
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one: (snip)
http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#wait%28long%29
When waiting upon a Condition, a "spurious wakeup" is permitted to occur, in general, as a concession to the underlying platform semantics. This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for. An implementation is free to remove the possibility of spurious wakeups but it is recommended that applications programmers always assume that they can occur and so always wait in a loop.
http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/locks/Condition.html
2013-10-18追記‥JDK1.4まではJava公式ドキュメントで明記されていなかったとのこと。
waitがnotifyがなくても再開することがあるという問題で、JavaDocではObject#waitでは﹁スプリアスウェイクアップ﹂、Condition#awaitでは﹁見せかけの起動﹂とかかれてます。
そのため、Object#waitやCondition#awaitは、再開条件でのループで囲む必要があります。
これはEffective Javaで指摘されたことにより有名になりました。そのため、JDK1.4までのJavaDocには記述がありません。
きしだのHatena - 正しいスレッドプログラム
関連URL