如果你的线程需要
- 改变某个变量,改动完成之后通知另一个线程
- 持续等待,直到收到某一个线程发来的通知
那么就可以放心食用std::condition_variable了。
下面用一个例子来介绍什么是condition variable以及怎么使用:
妈妈和儿子的故事:
儿子在外面玩,衣服脏了。
儿子说:“妈妈帮我洗衣服”。
妈妈说:“好的吧”。
那么总结起来,可以用两个functions来表示这个故事:
- 妈妈洗衣服 = void clean_laundry()
- 儿子在外面玩,让妈妈洗衣服 = void play_around()
首先我们定义好如下一些变量和辅助functions:
std::mutex mtx;
std::condition_variable CV;
enum laundry {clean, dirty};
laundry son_laundry = laundry::clean;
bool is_laundry_clean(){
return son_laundry == laundry::clean;
}
bool is_laundry_dirty(){
return son_laundry == laundry::dirty;
}
这个就是我们故事的第一个functions,它会检查儿子当前的衣服是不是脏的。
如果是脏的,就将其清洗。
如果不是脏的,就等待儿子将衣服弄脏了然后清洗。
void clean_laundry(){
std::unique_lock<std::mutex> lock(mtx);
// wait() function会检查传入的is_laundry_dirty是否为真
// true: wait()返回,继续执行下一步
// false: wait()会解锁,把当前的锁让给别的线程,自己进入等待状态
CV.wait(lock, is_laundry_dirty);
std::cout << "mom: okay, I will clean your laundry" << std::endl;
son_laundry = laundry::clean;
std::cout << "mom: the laundry is clean now" << std::endl;
// 因为要通知其他线程来继续执行,而其他线程需要当前这个mutex的lock才可以执行,所以必须要先将当前mutex解锁
lock.unlock();
// notify_one()会唤醒调用wait(mutex, condition)之后陷入等待的线程。
// 被唤醒的线程需要重新获取mutex lock,并且再次检查传入的条件是否为真
// 如果为true,线程继续执行,如果为false,线程继续等待
CV.notify_one();
}
故事的第二个functions,儿子在外面玩,如果将衣服弄脏了,就告诉妈妈让她清洗,在衣服洗好前,儿子都不能出去玩(因为没有衣服换了)。
直到妈妈通知他,衣服已经洗好了。
void play_around(){
std::cout << "Son is playing around and has laundry dirty" << std::endl;
{
std::lock_guard<std::mutex> lock(mtx);
son_laundry = laundry::dirty;
}
std::cout << "son: hey mom, can you please help me clean the laundry?" << std::endl;
// son tells his mom that his laundry is dirty now
CV.notify_one();
{
std::unique_lock<std::mutex> lock(mtx);
// 如果is_laundry_clean是true: 那么儿子可以结束等待,出去玩
// 如果is_laundry_clean是false;儿子必须一直等待
CV.wait(lock, is_laundry_clean);
}
std::cout << "son: wow, I get a clean laundry, thank you mom!" << std::endl;
}
注意,condition variable只能和std::unique_lock一起使用。具体请参见C++ Locks
可以看到,线程clean_laundry()和play_around()这两个线程都需要:
- 改变某个变量,改动完成之后通知另一个线程
- 持续等待,直到收到某一个线程发来的通知
所以它们使用condition variable来相互通信:
clean_laundry在son_laundry = laundry::dirty的时候执行,在son_laundry = laundry::clean的时候等待。
play_around在son_laundry = laundry::clean的时候执行,在son_laundry = dirty的时候等待。
最后,我们可以试验一下上面的程序:
int main(){
std::thread mother(clean_laundry);
std::thread son(play_around);
mother.join();
son.join();
return 0;
}
结果如下,一个完整的故事:
Son is playing around and has laundry dirty
son: hey mom, can you please help me clean the laundry?
mom: okay, I will clean your laundry
mom: the laundry is clean now
son: wow, I get a clean laundry, thank you mom!