使用condition_variable搭配atomic_flag时,是否必须使用unique_lock<mutex>?
我需要实现一个每10秒执行一次任务的std::jthread,同时允许在睡眠期间通过信号终止线程,写了如下代码:
void ThreadMain(std::condition_variable& cv, const std::atomic_flag& stop) { [[maybe_unused]] std::mutex mtx; [[maybe_unused]] std::unique_lock<std::mutex> lock(mtx); for(;;) { DoSomeTask(); if(cv.wait_for(lock, 10s, [&stop]() { return stop.test(); })) { return; } } } int main() { std::condition_variable cv; std::atomic_flag stop; std::jthread thread(&ThreadMain, std::ref(cv), std::cref(stop)); ... }
终止线程时执行:
stop.test_and_set(); cv.notify_all();
我的疑问是:既然已经用了std::atomic_flag,完全不需要这个unique_lock<mutex>,但condition_variable必须绑定锁才能调用wait_for(目前标准里没有std::atomic_flag::wait_for,不然刚好适配这个场景)。我是不是遗漏了什么?当前标准下只能这么写,还是陷入了XY问题,有更简洁的实现方式?虽然这个没用到的std::mutex几乎没影响,但我还是很在意。
补充:我知道可以用jthread::request_stop和stop_token,但还是需要condition_variable(进而需要占位锁)来实现wait_for功能。
当前C++标准里std::condition_variable确实必须搭配std::unique_lock<std::mutex>使用,哪怕这个锁只是占位用的。不过有几种更简洁的实现方式可以避免这种“无用锁”的情况:
方案1:原子变量+短轮询睡眠
不需要condition_variable,直接用std::this_thread::sleep_for结合原子变量的轮询检查,虽会有最多100ms的延迟(可调整),但实现简单:
void ThreadMain(const std::atomic_flag& stop) { for(;;) { DoSomeTask(); // 分100次短睡眠,每次检查终止信号 for(int i = 0; i < 100; ++i) { if(stop.test()) return; std::this_thread::sleep_for(100ms); } } } int main() { std::atomic_flag stop = ATOMIC_FLAG_INIT; std::jthread thread(&ThreadMain, std::cref(stop)); // 终止线程 stop.test_and_set(); }
方案2:使用std::condition_variable_any+空锁
std::condition_variable_any支持绑定任何满足BasicLockable要求的类型,我们可以自定义一个空锁类型,避免创建实际的std::mutex:
struct NullLock { void lock() {} void unlock() {} }; void ThreadMain(std::condition_variable_any& cv, const std::atomic_flag& stop) { NullLock mtx; std::unique_lock<NullLock> lock(mtx); for(;;) { DoSomeTask(); if(cv.wait_for(lock, 10s, [&stop]() { return stop.test(); })) { return; } } } int main() { std::condition_variable_any cv; std::atomic_flag stop = ATOMIC_FLAG_INIT; std::jthread thread(&ThreadMain, std::ref(cv), std::cref(stop)); // 终止线程 stop.test_and_set(); cv.notify_all(); }
方案3:结合std::stop_token与std::condition_variable_any
利用C++20的std::stop_token和std::stop_callback,搭配condition_variable_any实现无额外冗余锁的等待,完全复用jthread原生停止机制:
void ThreadMain(std::stop_token st) { std::condition_variable_any cv; std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); // 注册停止回调,触发时通知条件变量 std::stop_callback cb(st, [&cv]() { cv.notify_all(); }); for(;;) { DoSomeTask(); // 等待10秒或收到停止信号 if(cv.wait_for(lock, 10s, [&st]() { return st.stop_requested(); })) { return; } } } int main() { std::jthread thread(&ThreadMain); // 终止线程 thread.request_stop(); }
内容的提问来源于stack exchange,提问作者Agritite

