C++条件变量
1.引入条件变量的原因:
首先我们想象一个场景,果农每秒摘一个苹果,无数的消费者排队等苹果,苹果摘好就拿走。为此场景写一个极简生产者消费者模型如下:
#include <thread>
#include <mutex>
#include <iostream>
#include <unistd.h>
std::mutex mtx;
void func1(int& apple)
{
while (1)
{
{
std::unique_lock<std::mutex> lck(mtx);
apple += 1;
}
usleep(1000 * 1000);
}
}
void func2(int& apple)
{
while (1)
{
std::unique_lock<std::mutex> lck(mtx);
if (apple>0) {
apple -= 1;
}
}
}
int main()
{
int apple = 0;
std::thread t1(func1,std::ref(apple));
std::thread t2(func2, std::ref(apple));
t2.join();
t1.join();
return 0;
}
这个程序逻辑比较简单,func1是生产者,func2是消费者。然而这种写法非常占用cpu。在运行前我的centos cpu是这样:
运行后是这样:
从第三行%Cpu(s)可以看出用户空间占cpu百分比由1.8左右变为6.4左右,从倒数第2行看多出的时间基本都在我们的main.out上。这只是一个简单的函数,而且仅有一个消费者线程。如果消费者线程多起来那消耗将非常大。
消耗的原因在于生产者1秒的沉睡时间里,消费函数使用while循环不断进行访问,这导致了无必要的消耗。如果给消费者设定沉睡时间,即每次访问间隔0.5s或1s虽然可以一定程度上缓解这个问题,但是还面临两个情况:
a.生产者生产好了但是消费者还在沉睡,导致资源(苹果)没有及时被使用。
b.消费者沉睡结束生产者沉睡未结束,导致仍然消耗cpu。
很多时候真实的生产者生产时间是不确定的,比如这次摘苹果用0.5s,下一次用1.5s,因此无法从时间上精细控制沉睡时间,因此需要有一种方式,能够在生产者没生产好时不占cpu,一直阻塞在那里直到生产好,因此我们需要引入条件变量。
2.条件变量使用:
条件变量需要引入头文件#include <condition_variable>。
条件变量可以和std::mutex结合一起使用,其中notify_one()和wait(),wait()可以让线程陷入休眠状态,notify_one()可以唤醒一个处于wait中的条件变量,notify_all()可以唤醒所有出于wait的条件变量。
使用条件变量的程序如下:
#include <thread>
#include <mutex>
#include <atomic>
#include <iostream>
#include <unistd.h>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cond;
void func1(int& apple)
{
while (1)
{
{
std::unique_lock<std::mutex> lck(mtx);
apple += 1;
cond.notify_one();//唤醒一个等待的线程
}
usleep(1000 * 1000);
}
}
void func2(int& apple)
{
while (1)
{
std::unique_lock<std::mutex> lck(mtx);
if (apple) {
std::cout << apple << '\n';
apple -= 1;
continue;
}
cond.wait(lck);//等待线程
}
}
int main()
{
int apple = 0;
std::thread t1(func1,std::ref(apple));
std::thread t2(func2, std::ref(apple));
t2.join();
t1.join();
return 0;
}
运行后再次查看top,可以发现%Cpu(s)变为1.2,远小于未引用条件变量的占用。
从func2里我们发现,在wait之前互斥锁已经锁住了互斥量,然而生产者可以一直生产,这是因为wait函数调用了互斥锁的unlock函数然后才阻塞。这也是条件变量使用的加锁方式必须是unique_lock,不能是lock_guard的原因。而lock_guard没有lock和unlock接口,而unique_lock提供了,所以条件变量必须使用unique_lock保护互斥量。