用一个可被安全中断的等待循环,来模拟类似 QTimer 的“周期触发”。
#include <thread>
#include <atomic>
#include <chrono>
#include <functional>
#include <condition_variable>
class Timer
{
public:
using Callback = std::function<void()>;
Timer() = default;
~Timer() { stop(); }
void start(int interval_ms, Callback cb)
{
stop();
m_running = true;
m_thread = std::thread([=]() {
std::unique_lock<std::mutex> lock(m_mutex);
while (m_running)
{
if (m_cv.wait_for(lock,
std::chrono::milliseconds(interval_ms),
[&]() { return !m_running; }))
break;
cb(); // 定时触发
}
});
}
void stop()
{
m_running = false;
m_cv.notify_all();
if (m_thread.joinable())
m_thread.join();
}
private:
std::thread m_thread;
std::atomic<bool> m_running{false};
std::condition_variable m_cv;
std::mutex m_mutex;
};一个常见的C++定时器实现(基于单独线程 + 条件变量的周期性定时器)。目标是低依赖(仅用 C++ 标准库)、精度较高(基于 steady_clock)、线程安全(无 busy-wait)且可随时安全中断的计时器。
不采用的方案
- while(true) + sleep_for → 无法及时 stop
- std::async → 生命周期不可控
- sleep_for 单独线程 → 无法中断等待
整体功能
每隔指定的毫秒数(interval_ms),在后台线程自动重复执行一次传入的回调函数cb(),直到调用stop()为止。
这里的回调用得很妙。因为回调的核心就是“你先把函数传给别人,别人在某个时机(未来)再反过来调用你的函数”
核心成员变量
| 成员 | 类型 | 作用 |
|---|---|---|
m_thread |
std::thread |
负责定时循环的后台线程 |
m_running |
std::atomic<bool> |
原子标志位,表示定时器是否应该继续运行(true=运行,false=停止) |
m_cv |
std::condition_variable |
条件变量,用来实现“等待指定时间”或“立即被唤醒停止” |
m_mutex |
std::mutex |
保护条件变量的互斥锁(条件变量必须搭配互斥锁使用) |
关键函数
1. start(int interval_ms, Callback cb)
void start(int interval_ms, Callback cb)
{
stop(); // 先停止旧的(如果有),保证“单线程单定时器”语义
m_running = true;
m_thread = std::thread([=]() { ... });
}- 先调用
stop()清理可能存在的旧线程(非常重要,避免线程泄漏) - 设置运行标志
- 启动新线程,捕获所有变量按值(
[=]),包括interval_ms和cb
线程里执行的lambda内容(最核心部分):
std::unique_lock<std::mutex> lock(m_mutex);
while (m_running)
{
if (m_cv.wait_for(lock,
std::chrono::milliseconds(interval_ms),
[&]() { return !m_running; }))
break;
cb(); // 定时触发
}这段代码的作用是:
只要 m_running 还是 true,就一直循环做下面事情:
1. 等待 最多 interval_ms 毫秒
2. 在等待期间如果有人通过 m_cv.notify_all() 唤醒,并且此时 !m_running 了 → 立刻退出循环
3. 如果是正常超时(等满了 interval_ms) → 执行一次回调 cb()
4. 回到 1 继续下一轮
wait_for 的第三个参数(谓词)非常关键,它实现了“可被外部打断的睡眠”。
2. stop()
void stop()
{
m_running = false; // 1. 先把标志位置成停止
m_cv.notify_all(); // 2. 立刻唤醒正在 wait_for 的线程
if (m_thread.joinable()) // 3. 如果线程还活着
m_thread.join(); // 等待它安全退出
}这是最安全的停止方式三部曲:
- 改标志位(原子操作,线程立刻能看到)
- 主动唤醒(防止线程还在睡几秒甚至几分钟)
- join 回收线程资源(析构函数也会调用 stop,所以不会泄漏)
调用方式
Timer t;
// 方式1:lambda(最推荐,灵活)
t.start(500, []{
std::cout << "lambda 方式\n";
});
// 方式2:普通函数
void myTick() { std::cout << "普通函数\n"; }
t.start(600, myTick);
// 方式3:带参数的函数(用 std::bind)
void printNumber(int n) { std::cout << "数字: " << n << "\n"; }
t.start(700, std::bind(printNumber, 42));
// 方式4:类成员函数(推荐用 lambda 捕获 this)
class Worker {
public:
void doWork() { std::cout << "工作...\n"; }
};
Worker w;
t.start(1200, [&w]{ w.doWork(); });优缺点对比
优点:
- 比较精确(依赖系统定时器精度)
- 停止很安全(不会出现线程跑飞或析构时还在执行回调)
- 资源占用低(一个线程搞定)
- 支持任意可调用对象(lambda、函数指针、std::bind、functor等)
有待优化:
- 定时精度受操作系统调度影响(通常10ms左右,最差可能几十ms)
- 回调执行时间如果 > interval_ms,会出现“跳拍”(后续定时会延迟)
- 只有一个线程,回调里如果阻塞,会阻塞下一次计时
- 析构时会阻塞等待线程退出(如果是关键路径可能不合适)
❤️ 转载文章请注明出处,谢谢!❤️