标准C++无跨平台Timer类,推荐用std::thread+std::condition_variable实现可取消一次性定时器;周期性触发应基于主循环+std::chrono::steady_clock手动判断,避免阻塞和精度陷阱。

标准 C++ 没有内置的、跨平台的 Timer 类,所以“实现一个简单计时器”本质是封装系统级异步/轮询机制。直接用 std::this_thread::sleep_for + 循环是最易错的方式,不推荐用于真实计时逻辑。
用 std::thread + std::condition_variable 实现可取消的一次性定时器
这是最常用且可控的方案:启动一个分离线程,在指定延迟后通知回调,同时支持中途取消。关键不是“每隔多久执行”,而是“延迟多久后执行一次”。
常见错误现象:std::this_thread::sleep_for 在主线程里阻塞,导致 UI 冻结或无法响应中断;忘记加 std::mutex 保护取消标志,引发数据竞争。
- 使用场景:网络超时、按钮防抖、延时初始化
- 必须用
std::atomic或带锁的bool记录m_cancelled,否则多线程读写未同步会 UB - 不要在回调中直接操作 GUI 控件(如 Qt 的
QWidget),应发信号或投递到主线程 -
std::condition_variable::wait_for比裸sleep_for更利于响应取消
class SimpleTimer {
std::thread m_thread;
std::atomic m_cancelled{false};
std::function m_callback;
public:
explicit SimpleTimer(std::function cb) : m_callback(std::move(cb)) {}
void start(std::chrono::milliseconds delay) {
if (m_thread.joinable()) m_thread.join();
m_cancelled = false;
m_thread = std::thread([this, delay] {
std::mutex mtx;
std::condition_variable cv;
std::unique_lock lk(mtx);
cv.wait_for(lk, delay, [this] { return m_cancelled.load(); });
if (!m_cancelled.load()) m_callback();
});
m_thread.detach(); // 或者由使用者管理生命周期
}
void cancel() { m_cancelled = true; }
};
避免用 std::alarm 或 setitimer(Linux)做通用计时器
这些是信号驱动的底层接口,SIGALRM 不能安全调用大多数 C++ 对象(如 std::cout、std::vector::push_back),且信号处理函数中禁止 malloc/new、锁、IO 等操作。
立即学习“C++免费学习笔记(深入)”;
使用场景仅限极简嵌入式或信号处理专用模块;现代 C++ 项目中基本被弃用。
-
std::signal(SIGALRM, handler)注册的函数必须是 async-signal-safe 的,C++ 成员函数无法直接注册 - 多个
SimpleTimer实例共用同一个SIGALRM,无法区分来源 - Windows 下完全不可用,零跨平台性
需要周期性触发?用 std::chrono + 主循环手动判断(非阻塞)
GUI 应用、游戏主循环、嵌入式主干逻辑中,不应让计时器自己开线程,而应在每帧检查是否到期——这是最稳定、最易调试的方式。
性能影响:每次调用 std::chrono::steady_clock::now() 开销极小(通常几纳秒),远低于线程调度或系统调用。
- 存储下次触发时间点(
std::chrono::time_point),而非倒计时剩余毫秒数,避免累积误差 - 用
if (now >= next_fire) { callback(); next_fire += interval; },别用while防止卡顿导致连发 - 不要把
std::this_thread::sleep_for放进主循环来“省电”,它会让响应延迟不可控
class PeriodicTimer {
std::chrono::steady_clock::time_point m_next_fire;
std::chrono::milliseconds m_interval;
std::function m_callback;
public:
PeriodicTimer(std::chrono::milliseconds interval, std::function cb)
: m_interval(interval), m_callback(std::move(cb)) {
reset();
}
void reset() {
m_next_fire = std::chrono::steady_clock::now() + m_interval;
}
bool update() { // 返回 true 表示本次触发了回调
auto now = std::chrono::steady_clock::now();
if (now >= m_next_fire) {
m_callback();
m_next_fire += m_interval;
return true;
}
return false;
}
};
第三方库选型提醒:别为简单需求引入 heavy 依赖
Boost.Asio 的 boost::asio::steady_timer 功能完备,但强制绑定 io_context 和 strand,对单次延时任务属于杀鸡用牛刀;Qt 的 QTimer 依赖整个 Qt 框架,无法用于 headless 服务端。
真正容易被忽略的点:计时精度不等于系统时钟精度。std::chrono::steady_clock 在 Linux 上通常基于 CLOCK_MONOTONIC,但实际唤醒延迟受调度器影响,10ms 定时可能偏差 ±2ms —— 这不是 bug,是操作系统本质限制。硬实时场景必须用 RTOS 或内核模块。











