当一个线程等待另一个线程完成任务时,有几种选择。
第一种,线程持续性的检查共享数据标志。但是这样会造成性能的浪费,而且重复性的访问势必会对数据进行长时间的上锁,从而造成其他线程无法访问,这样线程就会持续等待。
第二种,比第一种稍微好点,在线程等待时间内使用std::this_thread::sleep_for
进行周期性的间歇。但是在某些环境下会造成一些遗漏,例如一些高节奏游戏中很可能会造成丢帧,或者在一个实时应用中超越一个时间片。
第三种,使用c++多线程标准库中的“条件变量”去实现,即一个线程完成事会触发另外一个线程中的变量觉醒从而唤醒整个线程。
std::condition_variale
和std::condition_variable_any
都包含于<condition_variable>
头文件的声明中。std::condition_variale
需要搭配std::mutex使用,系统资源开销较小,对硬件要求低,作为首选。std::condition_variable_any
可以搭配任何互斥量,使用比较灵活,但是系统资源的开销大,对硬件要求较高,作为第二选择。
例1 使用std::condition_variale
处理数据等待
std::mutex mut;
std::queue<data_chunk> data_queue; //1
std::condition_variable data_cond;
void data_prparation_thread()
{
while(more_data_to_prepare())
{
data_chunk const data = preapare_data();
std::lock_guard<std::mutex> lk(mut);
data_queue.push(data); //2
data_cond.notify_one(); //3
}
}
void data_processing_thread()
{
while(true)
{
std::unique_lock<std::mutex> lk(mut); //4
data_cond.wait(lk, []{return !data_queue.empty();}); //5
data_chunk data = data_queue.front();
data_queue.pop();
lk.unlock(); //6
process(data);
if(is_last_chunk(data))
break;
}
}
首先,声明一个用来在线程中传递数据的队列(如步骤1所示)。data_prparation_thread()
函数中,当数据准备好的时候,对互斥量上锁用来锁定队列,并将准备好的数据推入队列中(如步骤2所示)。然后调用std::condition_variable::notify_one()
函数,对等待线程进行通知(如步骤3所示)。
另外,在另一个线程中,data_processing_thread()
函数首先对队列进行上锁,然后调用
std::condition_variable::wait()
函数判断队列是否为空。如果队列为空,则解锁互斥量,释放队列,并使线程进入阻塞状态或睡眠状态等待其他线程中的std::condition_variable::notify_one()
函数的唤醒。当该线程被唤醒时,将再次获取互斥锁并判断判断条件,若满足条件就从std::condition_variable::wait()
函数中返回,进入后续程序。
例2. 构建线程安全队列
#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
template<typename T>
class threadsafe_queue
{
private:
mutable std::mutex mut;
std::queue<T> data_queue;
std::condition_variable data_cond;
public:
threadsafe_queue()
{}
threadsafe_queue(threadsafe_queue const& other)//复制构造函数
{
std::lock_guard<std::mutex> lk(other.mut);
data_queue = other.data_queue;
}
void push(T new_value)
{
std::lock_guard<std::mutex> lk(mut);
data_queue.push(new_value);
data_cond.notify_one();
}
void wait_and_pop(T& value)
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]{return !data_queue.empty();}) //等待push函数中data_cond.notify_one()函数的唤醒
value = data_queue.front();
data_queue.pop();
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_lock<std::mutex> lk(mut);
data_cond.wait(lk, [this]{return !data_queue.empty();});//等待唤醒
std::shared_ptr<T> res(std::make_shared<T> (data_queue.front()));
data_queue.pop();
return res;
}
bool try_pop(T& value)
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return false;
value = data_queue.front();
data_queue.pop();
return true;
}
std::shared_ptr<T> try_pop()
{
std::lock_guard<std::mutex> lk(mut);
if(data_queue.empty())
return std::shared_ptr<T>();
std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
data_queue.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lk(mut);
return data_queue.empty();
}
};
在例2中有wait_and_pop()
和try_pop()
两种弹出函数,try_pop()
函数仅仅可以进行线程安全的弹出操作但是功能比较弱,无法进行等待。而wait_and_pop()
则可以进行判断和等待。