单次/重复定时器应使用std::chrono::steady_clock+std::thread实现:单次为sleep_for后执行回调并退出;重复则循环sleep_for+回调,用std::atomic控制运行状态,raii封装确保析构时安全join()。

用 std::chrono + std::thread 手写单次/重复定时器
标准 C++ 没有内置“定时器对象”,但靠 std::this_thread::sleep_for 配合 std::chrono 时间单位,就能实现可靠、跨平台的定时逻辑。关键不是“造轮子”,而是控制好线程生命周期和唤醒时机。
常见错误是直接在主线程里 sleep_for,导致整个程序卡死;或者用 while(true) 空转轮询,浪费 CPU。正确做法是把等待逻辑放进独立线程,并提供安全的停止机制。
- 单次定时:启动线程 →
sleep_for→ 执行回调 → 线程退出 - 重复定时:循环中每次
sleep_for→ 执行回调 → 检查是否需继续 - 必须用
std::atomic<bool></bool>控制运行状态,避免竞态和线程泄漏 - 别用
std::clock或CLOCKS_PER_SEC,它们精度低且不单调;一律用std::chrono::steady_clock
std::atomic<bool> running{true};
std::thread t([&]{
while (running) {
std::this_thread::sleep_for(2s);
if (!running) break;
do_work(); // 你的回调
}
});
// 停止时:running = false; t.join();
</bool>
避免 std::thread 被析构时调用 terminate()
这是新手最常踩的坑:局部 std::thread 对象离开作用域时还没 join() 或 detach(),程序直接崩溃,报错信息通常是 std::thread::~thread: Assertion `__joinable == false` failed。
- 永远不要让
std::thread对象被自动析构——除非你明确调用了detach()(但 detach 后无法控制、易引发资源泄漏) - 推荐封装成 RAII 类型,构造时启动线程,析构时自动
join()(前提是已设好停止标志) - 如果线程可能长期运行,析构前务必先设置
running = false,再join(),否则join()会永久阻塞 - 别在回调里抛异常传到线程入口函数外,
std::thread不捕获它,会导致terminate()
Windows 下用 CreateTimerQueueTimer 还是坚持标准 C++?
Windows API 提供了内核级定时器(如 CreateTimerQueueTimer),精度高、不占线程、支持大量并发定时任务。但它完全不跨平台,且回调函数限制多(不能捕获 lambda、不能直接访问 this 指针),实际维护成本远高于手写线程定时器。
- 除非你在做高性能服务端,需要同时管理上万定时任务,且对误差要求
-
CreateTimerQueueTimer的回调运行在线程池中,无法保证顺序,也难以与对象生命周期绑定 - 跨平台项目一律用
std::chrono+std::thread,Linux/macOS 下同样稳定 - 若真需要更高精度,优先考虑调整
sleep_for前后的时钟校准(比如记录上次触发时间,下次按绝对时间点对齐),而非换 API
为什么不用 alarm() 或 setitimer()?
这些 POSIX 信号机制看似轻量,但和 C++ 对象模型水火不容:信号是异步的,无法安全调用非 async-signal-safe 函数(比如 std::cout、new、std::vector::push_back),也不能直接调用成员函数或捕获变量。
- 一旦在信号处理函数里做了禁止操作,行为未定义,常见表现是随机 crash 或死锁
- 信号定时器只能全局生效,无法为每个对象单独配置周期或回调
- C++20 的
std::jthread和std::stop_token已提供更安全的协作式中断,比信号更可控 - 现代 C++ 工程中,几乎没人再用
alarm()实现业务定时逻辑
真正难的不是“怎么启动一个延时”,而是“怎么在对象销毁时确保定时线程干净退出”——这要求你把停止逻辑和线程生命周期绑死,而不是依赖外部干预。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











