正确做法是用单个后台线程轮询优先队列中的定时任务,配合condition_variable等待最近到期时间;回调前用weak_ptr.lock()检查对象有效性;取消仅标记状态并唤醒线程,需协作式中断长耗时操作。

std::chrono + std::thread 怎么避免重复启动定时器
多个定时任务共用一个线程池时,std::thread 不能反复 join() 或 detach() —— 一旦结束就不可重用。直接为每个定时器起新线程会快速耗尽系统资源,还可能因未同步销毁导致 std::system_error: Invalid argument。
正确做法是用单个后台线程轮询任务队列,配合 std::condition_variable 等待下一个最近到期时间:
- 所有定时任务注册进一个
std::priority_queue(按std::chrono::steady_clock::time_point排序) - 主线程只负责插入/取消任务,不碰线程
- 后台线程用
cv.wait_until(lock, next_time)阻塞,唤醒后检查是否被取消或已过期 - 取消操作不是删节点,而是设一个
std::atomic<bool></bool>标志位,执行前再检查一次
std::shared_ptr + weak_ptr 如何防止定时器回调访问已析构对象
这是最常踩的坑:定时器还在运行,但持有回调对象的 std::shared_ptr 已被释放,回调里调用 this->do_something() 就是野指针。
必须用 std::weak_ptr 持有目标对象,在真正触发回调前尝试提升:
立即学习“C++免费学习笔记(深入)”;
- 注册定时器时传入
std::weak_ptr<myclass></myclass>而非std::shared_ptr - 回调函数开头写
auto ptr = obj_weak.lock(); if (!ptr) return; - 不要在 lambda 捕获列表里直接捕获
shared_ptr,否则延长生命周期掩盖问题 - 如果回调需多次访问成员,提升后用
ptr.get()或直接解引用,别反复调lock()
std::timer_queue 不可用?Windows 和 Linux 的替代方案差异
C++ 标准库至今没有 std::timer_queue(提案 P1029 还在讨论中),得自己搭。不同平台底层机制不同,影响精度和并发行为:
- Linux 下可基于
timerfd_create()+epoll实现高精度、低唤醒次数的调度,但需手动管理文件描述符生命周期 - Windows 下推荐用
CreateTimerQueueTimer(),它由系统线程池托管,但回调函数不能抛异常,且不保证调用顺序 - 跨平台项目建议统一用
std::this_thread::sleep_until()轮询,虽然有毫秒级误差,但逻辑清晰、无平台依赖 - 别用
std::this_thread::sleep_for()循环等待——系统时钟跳变或挂起会导致严重偏移
cancel() 后为什么定时器还在执行?
常见错误是以为调了 cancel() 就立刻终止正在运行的回调,其实它只标记“不再触发下一次”,当前回调仍在执行中。
要真正中断进行中的操作,得靠协作式取消:
- 每个定时器关联一个
std::atomic_bool cancel_requested{false} - 在回调内部关键点插入
if (cancel_requested.load()) return; -
cancel()函数除了置标志位,还要调用cv.notify_all()唤醒等待线程,让它及时检查状态 - 长耗时操作(如网络请求、文件读写)需配合
std::stop_token(C++20)或自定义中断点,不能只靠标志位
真正的难点不在启动定时器,而在让取消动作既及时又不破坏对象生命周期——多数崩溃都发生在“刚删对象,回调刚好进来”那一瞬间。









