結局のところ のように、_condition_variable::wait_for
_は実際には_condition_variable::wait_for_or_possibly_indefinitely_longer_than
_と呼ばれる必要があります。これは、実際にタイムアウトして戻る前にロックを再取得する必要があるためです。
デモについては このプログラム を参照してください。
「ほら、本当に2秒秒しかない。その時点でmyPredicate()
がまだfalseの場合、 /またはロックがまだロックされている、私は気にしない、関係なく続行し、それを検出する方法を教えてください。」
何かのようなもの:
_bool myPredicate();
auto sec = std::chrono::seconds(1);
bool pred;
std::condition_variable::cv_status timedOut;
std::tie( pred, timedOut ) =
cv.really_wait_for_no_longer_than( lck, 2*sec, myPredicate );
if( lck.owns_lock() ) {
// Can use mutexed resource.
// ...
lck.unlock();
} else {
// Cannot use mutexed resource. Deal with it.
};
_
condition_variable
のロックを悪用していると思います。時間のかかる作業を保護するためではなく、状態を保護するためだけのものです。
あなたの例は、mutex
を2つに分割することで簡単に修正できます。1つはクリティカルセクション用で、もう1つはready
条件の変更を保護するためのものです。変更されたフラグメントは次のとおりです。
typedef std::unique_lock<std::mutex> lock_type;
auto sec = std::chrono::seconds(1);
std::mutex mtx_work;
std::mutex mtx_ready;
std::condition_variable cv;
bool ready = false;
void task1() {
log("Starting task 1. Waiting on cv for 2 secs.");
lock_type lck(mtx_ready);
bool done = cv.wait_for(lck, 2*sec, []{log("Checking condition..."); return ready;});
std::stringstream ss;
ss << "Task 1 finished, done==" << (done?"true":"false") << ", " << (lck.owns_lock()?"lock owned":"lock not owned");
log(ss.str());
}
void task2() {
// Allow task1 to go first
std::this_thread::sleep_for(1*sec);
log("Starting task 2. Locking and sleeping 2 secs.");
lock_type lck1(mtx_work);
std::this_thread::sleep_for(2*sec);
lock_type lck2(mtx_ready);
ready = true; // This happens around 3s into the program
log("OK, task 2 unlocking...");
lck2.unlock();
cv.notify_one();
}
それは出力です:
@2 ms: Starting task 1. Waiting on cv for 2 secs.
@2 ms: Checking condition...
@1002 ms: Starting task 2. Locking and sleeping 2 secs.
@2002 ms: Checking condition...
@2002 ms: Task 1 finished, done==false, lock owned
@3002 ms: OK, task 2 unlocking...
実際、_condition_variable::wait_for
_はまさにあなたが望むことをします。この例の問題は、_ready = true
_割り当てとともに2秒間のスリープをロックし、制限時間に達する前に条件変数が述語を評価することさえ不可能にすることです。
そのstd::this_thread::sleep_for(2*sec);
行をロックの外側に置き、自分の目で確かめてください。
「ほら、本当に2秒しかない。その時点でmyPredicate()がまだfalseであるか、ロックがまだロックされている場合は、気にしないで、関係なく続行する...」と表現する方法はありますか。
はい、方法はありますが、残念ながら_wait_for
_の場合は手動で行う必要があります。 Spurious Wakeup のため、_wait_for
_は無期限に待機します。次のようなループを想像してみてください。
_while(!myPredicate())
cv.wait_for(lock, std::chrono::duration::seconds(2);
_
スプリアスウェイクアップは、任意のプラットフォームでいつでも発生する可能性があります。あなたの場合、200ミリ秒以内に発生すると想像してください。このため、外部からの通知なしにwait_for()
がウェイクアップし、ループ状態でmyPredicate()
をチェックします。
予想どおり、条件はfalseになるため、ループはtrueになり、再びcv.wait_for(..)
が実行されます。新しい2秒が必要です。これが無限に実行される方法です。
その更新期間を自分で制御するか、最終的にwait_until()
で呼び出されるwait_for()
を使用します。