C++11多线程(3):条件变量

C++11对多线程进行的支持,我们可以直接跳过系统API直接在语言层面编写多线程程序,直接的好处就是多线程程序的可移植性得到了很大的提高。这篇文章主要介绍条件变量相关内容。

正在进行C++11多线程编程相关的学习吗?点击这里查看这个分类的更多文章。

互斥量不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量互斥量搭配使用。

使用场景

我们参考一个生产者和消费者通过队列来传递数据的例子,下面是消费者线程一种可能的实现。

std::mutex mtx;
std::list<int> queue;

// 生产者者通过此函数向队列中添加元素
void addToQueue(int n)
{

std::unique_lock<std::mutex> lck(mtx);
queue.push_back(n);
}

// 消费者从队列中取出元素
void threadComsumer()
{

while(true)
{
std::unique_lock<std::mutex> lck(mtx);
while(queue.empty())
{
lck.unlock();
std::this_thread::sleep_for(std::chrono::millseconds(10));
lck.lock();
}

int n = queue.front();
std::cout << n << std::endl;
queue.pop_front();
}
}

threadComsumer()函数中,消费者通过定期检查队列中是否有数据,如果有则弹出数据,否则就通过sleep等待10毫秒,在这里,我们并不知道具体应该sleep多长时间。

  • 如果sleep时间过短,消费者线程就会频繁占用CPU资源,并且频繁占用mutex会导致生产者线程不能及时给mtx对象上锁。
  • 如果sleep时间过长,当队列里有元素时消费者就不能及时处理,导致效率上的损失。

那么具体应该sleep多长时间呢?我们可以借助条件变量来解决这个问题。

std::condition_variable

在C++11中,我们可以使用condition_variable对象来让当前线程阻塞等待,直到另一个线程通知当前线程继续执行。一个condition_variable主要有两类方法:

Wait 类型的方法

Wait类型方法可以让线程进入sleep状态,直到满足某些条件:

  • wait:当前线程进入等待状态,直到另一个线程调用Notify类型的方法
  • wait_for:当前线程进入等待状态,直到另一个线程调用Notify类型的方法,或者等待时间超过某一个阈值
  • wait_until:当前线程进入等待状态,直到另一个线程调用Notify类型的方法,或者到达某一个时间点

Notify 类型的方法

Notify类型方法可以唤醒所有处于wait状态的线程:

  • notify_one:从所有等待的线程中随机选择唤醒一个线程
  • notify_all:唤醒所有处于等待中的线程
    如果当前没有线程处于等待状态,那么notify函数什么也不做。

需要注意的是,如果你使用的条件变量和互斥量是一一对应的,那么使用notify_one或者notify_all并没有太大的区别。如果你有多个互斥量对应一个条件变量,那么使用notify_one并不一定能唤醒正确的线程,导致线程永远等待的问题。这是因为notify_one只是随机唤醒一个正在等待对应条件变量的线程,因为这个条件变量对应多个互斥量,所以notify_one有概率会唤醒错误的线程,导致异常。而notify_all是唤醒所有等待对应条件变量的线程,不会导致

使用条件变量

借助条件变量,我们可以将上面的代码改写为:

std::mutex mtx;
std::condition_variable cv;
std::list<int> queue;

// 生产者者通过此函数向队列中添加元素
void addToQueue(int n)
{

std::unique_lock<std::mutex> lck(mtx);
queue.push_back(n);
cv.notify_all();
}

// 消费者从队列中取出元素
void threadComsumer()
{

while(true)
{
std::unique_lock<std::mutex> lck(mtx);

while(queue.empty)
cv.wait(lck);

int n = queue.front();
std::cout << n << std::endl;
queue.pop_front();
}
}

使用条件变量,我们可以避免固定时间的sleep调用,既保证了运行效率,又不会占用额外的CPU时间。

参考资料

condition_variable - C++ Reference

此文有用? 求鼓励!

显示 Gitment 评论