C++:如何在UI线程和worker std::thread之间使用std::condition_variable
C++ : How to use an std::condition_variable between UI thread & worker std::thread
我正试图使用来自C++11
的std::condition_variable来处理UI线程与;工作线程。
情形:
m_calculated_value
是一个经过复杂逻辑计算的值。这是UI线程的事件触发器所必需的。UI线程调用MyClass::GetCalculatedValue
来获取m_calculated_value
的值,该值需要由作为MyClass::ThreadFunctionToCalculateValue
的工作线程函数来计算。
代码:
std::mutex m_mutex;
std::condition_variable m_my_condition_variable;
bool m_value_ready;
unsigned int m_calculated_value;
// Gets called from UI thread
unsigned int MyClass::GetCalculatedValue() {
std::unique_lock<std::mutex> lock(m_mutex);
m_value_ready = false;
m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this));
return m_calculated_value;
}
bool MyClass::IsValueReady() {
return m_value_ready;
}
// Gets called from an std::thread or worker thread
void MyClass::ThreadFunctionToCalculateValue() {
std::unique_lock<std::mutex> lock(m_mutex);
m_calculated_value = ComplexLogicToCalculateValue();
m_value_ready = true;
m_my_condition_variable.notify_one();
}
问题:
但问题是m_my_condition_variable.wait
永远不会返回。
问题:
我做错了什么
让UI线程等待来自工作线程的条件变量信号是正确的方法吗?如何摆脱condition_variable由于工作线程函数中的错误而从未触发的情况?有没有办法让我在这里暂停?
试图理解它是如何工作的:
我在许多示例中看到,他们在循环检查condition_var.wait
周围布尔变量的状态时使用。在变量上循环的意义是什么当从其他线程调用notify_one
时,我不能期望m_my_condition_variable
从wait
返回吗?
最有可能发生的情况:您的工作线程拥有并持有互斥锁,直到它完成计算。主线程必须等待,直到它能够获取锁为止。在释放锁(在析构函数中)之前,worker将向CV发出信号,到那时,没有其他想要等待条件变量的线程可能已经获取到它仍然被通知线程占用的锁。因此,另一个线程在收到通知时从未有机会等待条件变量,因为它只是在通知事件发生后设法获取了锁,导致它无限等待。
解决方案是删除MyClass::ThreadFunctionToCalculateValue()中的锁获取,它根本不需要,或者至少不应该存在。
但不管怎样,你为什么要重新发明轮子?对于此类问题,创建了std::future:
auto future = std::async(std::launch::async, ComplexLogicToCalculateValue);
bool is_ready = future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
auto result = future.get();
在这里,您可以很容易地定义超时,不必担心condition_variables等。
当notify_one从其他线程调用时,我是否希望m_my_condition_variable在等待之外返回?
否,并非唯一。虚假的觉醒仍然可能发生。
看看这里的例子:
http://en.cppreference.com/w/cpp/thread/condition_variable
在下面的示例代码的注释中注意到对有问题代码的更改。当可以安全地计算新值时,您可能需要考虑使用与cppreference.com示例中使用的相同的"握手"进行同步(UI线程具有wait/notify,工作线程具有notice/wait)。
在条件变量等待之前,需要锁定锁。等待将解锁,等待通知,然后锁定,并使用谓词函数检查是否准备就绪,如果没有准备就绪(虚假唤醒),则重复该循环。
在notify_one之前,锁应该被解锁,否则等待会被唤醒,但无法获得锁(因为它仍然被锁定)。
std::mutex m_mutex;
std::condition_variable m_my_condition_variable;
bool m_value_ready = false; // init to false
unsigned int m_calculated_value;
// Gets called from UI thread
unsigned int MyClass::GetCalculatedValue() {
std::unique_lock<std::mutex> lock(m_mutex);
m_my_condition_variable.wait(lock, std::bind(&MyClass::IsValueReady, this));
m_value_ready = false; // don't change until after wait
return m_calculated_value;
} // auto unlock after leaving function scope
bool MyClass::IsValueReady() {
return m_value_ready;
}
// Gets called from an std::thread or worker thread
void MyClass::ThreadFunctionToCalculateValue() {
std::unique_lock<std::mutex> lock(m_mutex);
m_calculated_value = ComplexLogicToCalculateValue();
m_value_ready = true;
lock.unlock(); // unlock before notify
m_my_condition_variable.notify_one();
}
或替代方案:
// Gets called from an std::thread or worker thread
void MyClass::ThreadFunctionToCalculateValue() {
{ // auto unlock after leaving block scope
std::lock_guard<std::mutex> lock(m_mutex);
m_calculated_value = ComplexLogicToCalculateValue();
m_value_ready = true;
} // unlock occurs here
m_my_condition_variable.notify_one();
}
- C++中std::resize(n)和std::shrink_to_fit之间的区别
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 如何使 std::sort 在 std::swap 和我的命名空间的模板化交换之间没有名称冲突?
- std::is_convertible 和 std::convertible_to 之间的区别(在实践中)?
- std::filesystem 和 std::experimental::filesystem 之间的路径差异
- 在什么条件下使用 std::memcpy 在对象之间复制是安全的?
- std::memmove在同一对象之间是否始终安全
- Cxx.jl 在 Julia Complex 和 std::complex 之间进行转换
- 如何在 std::string 和 Aws::String 之间进行转换?
- std::weak_ptr 和相应的 std::shared_ptr 之间是否存在数据竞争?
- std::fabs(a * b) 和 std::fabs(a) * std::fabs(b) 之间的区别
- 如何在窗口之间移动 std::unique_ptr 而不会冒内存泄漏的风险?
- 简单使用 std::atomic 在两个线程之间共享数据
- "+=" 操作在类型之间不起作用 std::复杂<double>和__complex__双精度
- "std::function<double(double)>"到"double (*)(double)"之间的转换
- malloc 和 calloc 与 std::string 之间的区别
- std::random_device和std::mt19937_64之间有什么区别
- 是std::memcpy在不同的可复制类型之间的未定义行为
- 我收到void main()和使用命名空间std;之间的冲突;?我正在使用代码块
- iostream和命名空间std之间的关系是什么?