std::chrono + std::mutex 不足以构成线程安全计时器,因其仅能保护状态读写,无法实现定时触发、回调调度、可取消性及防阻塞特性;需结合 std::condition_variable、wait_until、锁外回调等机制。

为什么 std::chrono + std::mutex 不足以构成“线程安全计时器”
直接套用 std::chrono::steady_clock 和一个 std::mutex 保护的整型变量,只能算“线程安全的计数器”,不是真正的计时器。真正的计时器要能:在指定时间点触发回调、支持重复/单次执行、可取消、不因调用方阻塞而丢失 tick。常见错误是把 std::this_thread::sleep_for 放在锁内,导致整个系统被拖慢;或者用 while 轮询,浪费 CPU。
用 std::condition_variable 实现低开销等待
核心思路是让工作线程在条件变量上等待,而不是忙等或长睡。唤醒时机由定时逻辑决定,而非靠锁住整个状态去检查。
- 用
std::chrono::time_point记录下次触发时间,每次触发后更新它 - 等待时用
cv.wait_until(lock, next_time),这样即使被虚假唤醒也能重新校准 - 每次 wait 前先解锁,避免阻塞其他线程修改计时器状态(如取消)
- 回调函数必须在锁外执行,否则可能死锁或拖慢调度线程
void Timer::run() {
std::unique_lock<:mutex> lock(mutex_);
while (active_) {
auto now = Clock::now();
if (next_time_
<h3>
<code>std::atomic<bool></bool></code> 不能替代 <code>std::mutex</code> 保护整个状态</h3>
<p>有人试图只用 <code>std::atomic<bool> active_</bool></code> 控制启停,但这是危险的:取消操作需要同时修改 <code>active_</code>、重置 <code>next_time_</code>、并通知等待线程,这三者必须原子——而 <code>std::atomic</code> 不支持多字段原子操作。常见错误现象是调用 <code>cancel()</code> 后,线程仍在 sleep 中,直到超时才退出,造成延迟取消。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)"><img
src="https://img.php.cn/upload/manual/000/000/001/5d6de31fedca2993.png" alt="C函数速查手册(CHM版)" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)" class="overflowclass">C函数速查手册(CHM版)</a>
<p class="overflowclass">C函数速查手册(CHM版)</p>
</div>
<a rel="nofollow" href="/xiazai/shouce/1510" title="C函数速查手册(CHM版)" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<ul>
<li>必须用 <code>std::mutex</code> 保护 <code>active_</code>、<code>next_time_</code>、<code>repeat_</code> 等共享状态</li>
<li>
<code>cancel()</code> 内需调用 <code>cv_.notify_one()</code>,确保等待线程能立即响应</li>
<li>不要在回调中调用 <code>cancel()</code> —— 可能正持有锁,造成自死锁</li>
</ul>
<h3>构造与析构时的资源生命周期陷阱</h3>
<p>计时器对象析构时,工作线程可能还在运行。若直接销毁,<code>callback_</code> 可能访问已释放的闭包或 this 指针,引发 UBSAN 报错或崩溃。</p>
<ul>
<li>析构函数里必须先 <code>cancel()</code>,再 <code>join()</code> 工作线程</li>
<li>如果允许异步销毁(比如 shared_ptr 管理),就得用 <code>std::shared_ptr</code> 捕获 this,并在线程内做 weak_ptr.lock() 判空</li>
<li>避免在 lambda 回调中直接捕获局部变量,改用值捕获或显式传参</li>
<li>测试时故意在回调里 sleep(100ms),再立刻销毁对象,就能暴露这类问题</li>
</ul>
<p>真正难的不是“怎么让计时器跑起来”,而是“怎么让它在任意时刻被取消、被销毁、被并发修改,都不崩”。所有看似简单的封装,只要漏掉 notify + join + 锁粒度控制中的一环,就会在高并发或快速启停场景下出问题。</p></:mutex>C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










