学习来源主要来自于 基本功 | 一文讲清多线程和多线程同步
去年的时候写过一个服务的 cache 模块,启一个线程定期从数据库中拉全量的数据存到本地的 rocksdb 中,并在服务退出的时候,实现线程的优雅退出,其中使用到了 condition_variable
,代码样例如下
1 | struct MongoCacheConfig { |
当时没太理解 condition_variable
的用法,是仿照项目代码中其它用到 condition_variable
的代码写的。今天又捋了一遍,大概搞明白了它的用法。单把调用的地方拎出来:
1 | void _refresh_collection_cache_loop() { |
首先,先加个锁 lck
,锁定 _stop
的状态,然后读取它的值,如果符合条件,则进入到循环中,执行完 _refresh_collection_cache
函数后,将 lck
传给 condition_variable
,它给先把这个锁 unlock 了,随后挂起这个线程,等待 notify_all
的调用。可以配置超时时间,如果超时了,则解除挂起,再继续执行 while 循环。
下面分析一下对象析构函数被调用时,同步数据线程的执行情况
如果是在执行
_refresh_collection_cache
时,则在执行完毕后,wait_for 的 predicate 满足条件,不阻塞,再执行 while 语句,退出如果是在
wait_for
时,则由析构函数中的notify_all
解除了wait_for
的挂起,再执行 while 语句,退出
咨询 gpt 的时候,它给的样例如下
1 |
|
上述代码中 worker_thread 函数的执行流程是,首先上锁(std::unique_lock<std::mutex> lck(mtx)
),当 ready 为 false 时,执行 cv.wait(lck)
释放锁,并阻塞住,直到 main 函数中调用 cv.notify_one()
后,执行后续的逻辑。至于为什么不能将 while (!ready)
改成 if (!ready)
,gpt 给出的解释是,可能存在假唤醒(spurious wakeup)的情况,这里就不深究了。
golang 中也有类似的接口 sync.Cond
,样例如下
1 | package main |