在 C++20 标准库中,std::condition_variable_any::wait()
家族支持 std::stop_token
用于广义线程取消,但 std::condition_variable
不支持。
P0660R10 Stop Token and Joining Thread, Rev 10 说:
<块引用>R6 中的新功能
condition_variable_any
而不是 consition_variable
以避免所有可能的竞争、死锁和意外的未定义行为。我认为以下代码可以安全地模拟 condition_variable::wait()
和 stop_token
取消。我错过了什么?是否有微妙的边缘情况?
template<class Lock, class Predicate>
bool cv_wait_with_stoken(
std::condition_variable& cv, Lock& lock, std::stop_token stoken, Predicate pred)
{
std::stop_callback callback{ stoken, [&cv]{ cv.notify_all(); } };
while (!stoken.stop_requested()) {
if (pred())
return true;
cv.wait(lock);
}
return pred();
}
答案 0 :(得分:3)
是的,存在竞争条件。
一般来说,对于条件变量,您必须在修改受保护状态(通常是变量)和向条件变量发出信号之间保持互斥锁一段时间。否则,您可能会错过信号。
让你的状态成为原子变量并不能避免这个问题。
cv 的等待代码首先检查状态。如果失败,它会自动地删除锁并等待信号。
如果您的停止令牌在检查后设置在该间隙中,但在等待之前,则停止令牌调用通知所有,条件变量不会选择通知所有。
cv.notify_all()
必须先获得该锁。这会打开一整罐蠕虫。
不要使用这段代码,它可能会由于双重锁定或其他无数事情而严重崩溃,但理论上它看起来像:
bool cv_wait_with_stoken(
std::condition_variable& cv,
Lock& lock,
std::stop_token stoken,
Predicate pred
) {
std::stop_callback callback{ stoken, [&cv]{
lock.lock();
lock.unlock();
cv.notify_all();
} };
while (!stoken.stop_requested()) {
if (pred())
return true;
cv.wait(lock);
}
return pred();
}
在那里try_lock
是可能的,我将不得不做很多艰苦的证明工作。