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 字节说明出错或被中断
use-after-free 和 thread leak 就跟着来了。









