std::this_Thread::sleep_for 不能当定时器用,因其仅阻塞当前线程、无回调、不可取消、不自动重复,仅适用于单次延时;伪定时循环会导致卡死、响应失效和精度偏差。

std::this_thread::sleep_for 为什么不能当定时器用
它只是让当前线程暂停,不触发回调、不自动重复、无法取消,本质是“延时”,不是“定时器”。真要轮询或等待某个时间点,它能凑合;但想在后台隔几秒执行一次 log_status() 或超时后发个通知,它直接失效。
常见错误现象:std::this_thread::sleep_for 套在 while 循环里做“伪定时”,结果主线程卡死、无法响应退出信号、精度漂移严重(尤其在 windows 上实际休眠比指定时间长)。
- 只适合单次阻塞等待,比如初始化后等 100ms 再继续
- 不要用它模拟周期任务——CPU 空转 + sleep 组合既耗电又不准
- 如果必须用,优先搭配
std::chrono::steady_clock,别用system_clock(可能被系统时间调整拖垮)
std::thread + std::condition_variable 实现可取消的单次定时器
这是 c++11 起最轻量、无外部依赖的靠谱方案,核心是让一个独立线程等待条件变量超时,到点后通知主逻辑,同时支持中途调用 cancel() 提前唤醒。
使用场景:http 请求超时控制、资源锁自动释放、GUI 操作倒计时反馈。
立即学习“C++免费学习笔记(深入)”;
关键参数差异:std::condition_variable::wait_until 接收的是绝对时间点(std::chrono::time_point),不是间隔;而 wait_for 是相对时长,容易因系统调度偏差累积误差。
示例片段:
class SimpleTimer { std::thread t_; std::mutex mtx_; std::condition_variable cv_; bool cancelled_ = false; public: template<typename Rep, typename Period, typename F, typename... Args> SimpleTimer(std::chrono::duration<Rep, Period> delay, F&& f, Args&&... args) { t_ = std::thread([this, delay, f = std::forward<F>(f), args = std::make_tuple(std::forward<Args>(args)...)]() mutable { auto tp = std::chrono::steady_clock::now() + delay; std::unique_lock<std::mutex> lk(mtx_); if (cv_.wait_until(lk, tp, [this]{ return cancelled_; })) { return; // 被 cancel } std::apply(f, args); }); } void cancel() { { std::lock_guard<std::mutex> lk(mtx_); cancelled_ = true; } cv_.notify_all(); } ~SimpleTimer() { if (t_.joinable()) t_.join(); } };
Windows 上用 SetTimer / CreateWaitableTimer 容易踩的坑
这两个 API 表面简单,但和 C++ 对象生命周期一结合就出事。典型问题是:定时器回调函数里访问已析构的对象成员,或者 SetTimer 返回的 UINT_PTR 被当成裸指针传进回调,结果对象早没了。
性能影响:Windows GUI 线程的 SetTimer 会向消息队列投递 WM_TIMER,若主线程忙于计算,消息积压,定时器就“跳帧”;而 CreateWaitableTimer 配合 WaitForSingleObjectEx 更准,但必须手动管理等待线程。
- 绝不要在回调里直接调用
this->do_something()—— 改用静态函数 +SetWindowLongPtr存句柄,或用std::shared_ptr持有对象并传入 Lambda 捕获 -
CreateWaitableTimer的bManualReset设为false才能自动重置,否则第二次WaitForSingleObject会立刻返回 - 跨 DLL 边界时,确保回调函数是
__stdcall,否则栈被破坏
linux 下 timerfd_create 怎么和 epoll 配合
这是 Linux 原生、高效、可嵌入事件循环的方案,timerfd_create 返回一个文件描述符,到期时 epoll_wait 就能感知到可读事件,不用额外线程。
兼容性注意:glibc 2.8+、内核 2.6.25+ 才支持;macos 和 Windows 完全不支持,别写死了。
容易漏的点:timerfd_settime 的 it_value 设为 0 表示“只设置间隔,不立即触发”,但很多人误设成非零导致第一次触发提前;另外,每次 read timerfd 必须读 8 字节(uint64_t),否则下次 epoll_wait 不再通知。
- 用
CLOCK_MONOTONIC,别用CLOCK_REALTIME(NTP 调整会让定时器乱跳) - Struct itimerspec 的
it_interval设为 0 表示只触发一次 - 务必检查
read()返回值,少于 8 字节说明出错或被中断
C++ 里没有“开箱即用”的跨平台定时器抽象,每个方案都得自己兜底处理取消、销毁、线程安全。最麻烦的不是写代码,而是厘清“谁负责 stop、谁负责 join、谁负责清理资源”——这些边界一旦模糊,use-after-free 和 thread leak 就跟着来了。