概念简介
条件变量(condition_variable)是一种同步原语,用于线程之间的协调。它允许一个或多个线程一起等待另一个线程的通知。条件变量依赖于互斥量(mutex),它被用来保护等待条件变量的共享数据,并在条件满足时通知等待的线程。
线程通过持有互斥锁来进入等待状态并等待从其它线程中获得信号释放,该线程可以获取到mutex。当其它线程以notify_one或notify_all的方式发出通知,等待线程被唤醒开始尝试重新获取互斥锁。这意味着只有在持有互斥锁时才可以进行等待或接收条件变量信号。
当某个线程调用条件变量的wait
方法时,它会释放占用的互斥锁lock
,因此,等待线程不会阻塞在互斥锁上,同时允许其他线程使用该锁。然后该线程阻塞,开始等待被notify_one
或 notify_all
唤醒。在唤醒之后,线程会重新尝试获得互斥锁lock
。如果互斥锁无法获得,则该线程将阻塞等待互斥量变得可用。如果可以获得互斥锁,则等待线程继续执行,并处理条件变量得到的通知。
锁是为了保护临界资源不被随意访问,条件变量是多线程之间相互通知,如资源是否可用的一种通信手段,两种机制往往配合使用;想象一下,如果没有条件变量的通知机制,那么等待线程就不能阻塞且让出CPU时间片的等待,而是定时或者死循环的等待,对整个系统资源来说,无非是一种浪费。
条件变量原型以及常用接口
lass condition_variable
{
public:
condition_variable() noexcept;
~condition_variable() noexcept;
condition_variable(const condition_variable&) = delete;
condition_variable& operator=(const condition_variable&) = delete;
void
notify_one() noexcept;
void
notify_all() noexcept;
void
wait(unique_lock<mutex>& __lock) noexcept;
template<typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<__clock_t, _Duration>& __atime)
{ return __wait_until_impl(__lock, __atime); }
template<typename _Clock, typename _Duration>
cv_status
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<_Clock, _Duration>& __atime);
template<typename _Clock, typename _Duration, typename _Predicate>
bool
wait_until(unique_lock<mutex>& __lock,
const chrono::time_point<_Clock, _Duration>& __atime,
_Predicate __p)
{
while (!__p())
if (wait_until(__lock, __atime) == cv_status::timeout)
return __p();
return true;
}
template<typename _Rep, typename _Period>
cv_status
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime);
template<typename _Rep, typename _Period, typename _Predicate>
bool
wait_for(unique_lock<mutex>& __lock,
const chrono::duration<_Rep, _Period>& __rtime,
_Predicate __p)
{
//......
return wait_until(__lock, __steady_clock_t::now() + __reltime,
std::move(__p));
}
private:
template<typename _Dur>
cv_status
__wait_until_impl(unique_lock<mutex>& __lock,
const chrono::time_point<__clock_t, _Dur>& __atime);
};
常用接口简介:
void wait(unique_lock<mutex>& __lock)
, 调用该函数的线程,主动释放锁,然后阻塞,等待条件变量通知,如果被唤醒,将重新去获得__lock锁,就可以正确进行临界资源的访问;notify_one() / notify_all()
唤起,通知一个或者所有等待阻塞在当前条件变量上的线程,通知前当前线程应该释放自己占用的锁,以便得到通知的线程能拿到锁访问临界资源;
template<typename _Predicate>
void
wait(unique_lock<mutex>& __lock, _Predicate __p)
{
while (!__p())
wait(__lock);
}
- 如上接口
void wait(unique_lock<mutex>& __lock, _Predicate __p)
,除了含有一个unique_lock锁对象参数,还有一个返回值为bool的表达式参数。该wait函数中循环的判断检查表达式P的返回值是否为真,如果为真,将尝试获取锁,获取成功将继续向下执行,否则将阻塞等待互斥锁可用;如果返回的是false,会释放锁,重新阻塞;
举个例子,一个线程访问一个消息队列, 若不为空,线程将可以获取锁,进行下一步操作
cv.wait(lock, [](){ return !msgQueue.empty(); })
bool wait_for(//...)
阻塞当前线程,直到条件变量被唤醒,或超时返回bool wait_until(//...)
for是一段时长,until是一个时间点- 静态函数
void notify_all_at_thread_exit(condition_variable&, unique_lock<mutex>)
,一般在线程的入口函数调用,注册其所持有的锁以及条件变量,从而可以在线程退出时释放锁并且通知等待在该条件变量上线程,其他线程可以通过条件变量收到通知并获取锁访问临界区。
//在一个线程方法中调用该方法,
{
std::unique_lock<std::mutex> lk(m);
std::notify_all_at_thread_exit(cond, std::move(lk), my_id);
// ... 做业务 访问临界区代码
//右括号退出前,释放锁,通知其他wait在当前cond上的线程
}
其他等待的线程:
{
std::unique_lock<std::mutex> lk(m);
cond.wait(lk); // 阻塞,等待条件变量通知
// ... 做业务 然后退出
}