C++:如何在 UI 线程和工作线程 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 永远不会 returns.

问题:
我做错了什么?

使 UI 线程等待来自工作线程的条件变量信号是否正确?我如何摆脱 condition_variable 由于工作线程函数错误而永远不会触发的情况?有什么办法可以在这里使用超时吗?

试图了解其工作原理:
我在许多示例中看​​到他们使用 while 循环检查 condition_var.wait 周围的布尔变量 的状态。在变量上循环有什么意义? 当从其他线程调用 notify_one 时,我不能期望 m_my_condition_variablewait 到 return 吗?

最有可能发生的事情: 您的工作线程拥有并持有互斥锁,直到计算完成。主线程必须等到它可以获取锁。 worker 将在 before 它释放锁(在析构函数中)之前向 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 等问题。

Cant I expect m_my_condition_variable to return out of wait when notify_one is called from other thread ?

No,不完全是。虚假唤醒仍然可能发生。

看看这个例子:

http://en.cppreference.com/w/cpp/thread/condition_variable

在下面的示例代码的注释中注明了对相关代码的更改。您可能需要考虑使用与 cppreference.com 示例中使用的相同的 "handshake" 来在可以安全计算新值时进行同步(UI 线程有一个等待/通知,工作线程有通知/等待)。

在条件变量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();
}