C++11 多线程 - Part 7:条件变量
翻译自:https://thispointer.com//c11-multithreading-part-7-condition-variables-explained/
在本文中,我们将通过示例讨论C++ 11多线程中条件变量的用法。
条件变量
条件变量是一种事件,用于在两个或多个线程之间进行信号传递。一个或多个线程可以等待它发出信号,而另一个线程可以发出信号。
C++ 11中的条件变量所需的头文件是
#include <condition_variable>
需要互斥锁以及条件变量。
条件变量实际如何工作
* 线程1调用条件等待变量,该变量在内部获取互斥量并检查是否满足所需条件。
* 如果没有满足条件,则释放锁并等待条件变量得到信号(线程被阻塞)。 条件变量的wait()函数以原子方式(?)提供这两种操作。
* 另一个线程,即像线程2一样在满足条件时发出条件变量信号
* 一旦条件变量得到信号,正在等待的线程1将恢复。 然后,它再次获取互斥锁,并检查与条件变量关联的条件是否实际满足或是否为上级调用。 如果有多个线程在等待,那么notify_one将仅解除一个线程。
* 如果是上级调用,则再次调用wait()函数。
std::condition_variable的主要成员函数是
Wait()
它使当前线程阻塞,直到信号通知条件变量或发生虚假唤醒为止。
它以原子方式释放附加的互斥锁,阻止当前线程,并将其添加到等待当前条件变量对象的线程列表中。 当某些线程在同一条件变量对象上调用notify_one()或notify_all()时,该线程将被解除阻塞。 它也可能会被虚假地解除阻塞,因此,每次解除阻塞后,都需要再次检查条件。
回调将作为参数传递给此函数,该函数将被调用以检查它是否是虚假调用或是否实际满足条件。
线程解锁后,
wait()函数重新获取互斥锁,并检查是否满足实际条件。 如果不满足条件,则再次自动释放附加的互斥锁,阻塞当前线程,并将其添加到等待当前条件变量对象的线程列表中。
notify_one()
如果有任何线程在同一条件变量对象上等待,则notify_one会解除封锁其中一个等待线程。
notify_all()
如果有任何线程在同一条件变量对象上等待,则notify_all解除封锁所有正在等待的线程。
让我们来看看如何使用条件变量
问题场景
假设我们正在构建一个基于网络的应用程序。 该应用程序执行以下任务,
1.与服务器进行一些handshaking
2.从XML文件加载数据。
3.对从XML加载的数据进行处理。
正如我们所见,任务1不依赖于任何其他任务,但是任务3依赖于任务2。因此,这意味着任务1和任务2可以由不同的线程并行运行以提高应用程序的性能。 因此,让我们将其分解为一个多线程应用程序,
线程1的职责是
* 与服务器进行一些handshaking。
* 等待线程2从XML加载数据
* 对从XML加载的数据进行处理。
线程2的职责是
* 从XML加载数据
* 通知另一个线程,即等待消息。
使用条件变量实现此目的的代码如下,
#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
using namespace std::placeholders;
class Application
{
std::mutex m_mutex;
std::condition_variable m_condVar;
bool m_bDataLoaded;
public:
Application()
{
m_bDataLoaded = false;
}
void loadData()
{
std::cout<<"loadData start"<<std::endl;
// Make This Thread sleep for 1 Second
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout<<"Loading Data from XML"<<std::endl;
// Lock The Data structure
std::lock_guard<std::mutex> guard(m_mutex);
// Set the flag to true, means data is loaded
m_bDataLoaded = true;
// Notify the condition variable
m_condVar.notify_one();
}
bool isDataLoaded()
{
if (m_bDataLoaded) {
std::cout << "load true" << std::endl;
} else {
std::cout << "load false" << std::endl;
}
return m_bDataLoaded;
}
void mainTask()
{
std::cout<<"Do Some Handshaking"<<std::endl;
// Acquire the lock
std::unique_lock<std::mutex> mlock(m_mutex);
// Start waiting for the Condition Variable to get signaled
// Wait() will internally release the lock and make the thread to block
// As soon as condition variable get signaled, resume the thread and
// again acquire the lock. Then check if condition is met or not
// If condition is met then continue else again go in wait.
m_condVar.wait(mlock, std::bind(&Application::isDataLoaded, this));
std::cout<<"Do Processing On loaded Data"<<std::endl;
}
};
int main()
{
Application app;
std::thread thread_1(&Application::mainTask, &app);
std::thread thread_2(&Application::loadData, &app);
thread_1.join();
thread_2.join();
return 0;
}
/*输出结果
Do Some Handshaking
load false
loadData start
Loading Data from XML
load true
Do Processing On loaded Data
*/