文章

C++定时器

C++定时器

该文是介绍简单的 C++ 定时器。

C++ 定时器

1. 简介

定时器是一种用于在指定时间后执行某个任务的机制。定时器广泛应用于操作系统、嵌入式系统、网络编程、GUI 应用等各种场景,主要用于:

  • 延迟执行(Delay Execution):在一段时间后执行某个任务(一次性任务)。
  • 周期性任务(Recurring Task):按照固定间隔重复执行某个任务。
  • 超时控制(Timeout):在特定时间内等待某个事件发生,否则触发超时逻辑。
  • 任务调度(Task Scheduling):在服务器或后台程序中按照时间间隔执行任务。

2. 实现方式

不同场景下可以采用不同的定时器实现方式:

方式特点
忙等(Busy Waiting)轮询检查时间,消耗 CPU,不推荐。
sleep + 轮询每隔一段时间 sleep() 然后检查是否到时,适用于简单任务,但精度受限。
信号驱动定时器通过 setitimer()timer_create() 设置定时器,由内核在到期时发送信号,适用于 Linux 系统。
基于多线程使用 std::thread + std::condition_variable 实现高效的定时器管理,适用于 C++ 标准库环境。
事件驱动(Event Loop)适用于 GUI 应用或服务器(如 epoll / select / libevent / Boost.Asio)。

3. 代码

timer.h 定时器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#ifndef __TIMER_H__
#define __TIMER_H__

#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <algorithm>

class Timer
{
public:
    Timer()
        : m_shutdown(false), m_nextId(1)
    {
        // 启动后台工作线程
        m_thread = std::thread(&Timer::worker, this);
    }

    ~Timer()
    {
        shutdown();
    }

    /// @brief 安排一个一次性任务,在 delay 毫秒后执行。
    /// @param delay 延迟时间(毫秒)
    /// @param task 回调函数
    /// @return 返回任务的唯一标识 id
    int schedule(std::chrono::milliseconds delay, std::function<void()> task)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        int                         id = m_nextId++;
        TimerTask                   newTask;
        newTask.id        = id;
        newTask.time      = std::chrono::steady_clock::now() + delay;
        newTask.interval  = std::chrono::milliseconds(0); // 0 表示一次性任务
        newTask.callback  = std::move(task);
        newTask.cancelled = false;
        m_tasks.push_back(std::move(newTask));
        m_cv.notify_one();
        return id;
    }

    /// @brief 安排一个周期性任务,初始 delay 毫秒后开始,之后每 interval 毫秒执行一次。
    /// @param delay 初始延迟(毫秒)
    /// @param interval 周期间隔(毫秒)
    /// @param task 回调函数
    /// @return 返回任务的唯一标识 id
    int scheduleRecurring(std::chrono::milliseconds delay,
                          std::chrono::milliseconds interval,
                          std::function<void()>     task)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        int                         id = m_nextId++;
        TimerTask                   newTask;
        newTask.id        = id;
        newTask.time      = std::chrono::steady_clock::now() + delay;
        newTask.interval  = interval;
        newTask.callback  = std::move(task);
        newTask.cancelled = false;
        m_tasks.push_back(std::move(newTask));
        m_cv.notify_one();
        return id;
    }

    /// @brief 根据任务 id 取消任务(无论一次性还是周期性任务)
    /// @param taskId 任务 id
    void cancel(int taskId)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        for (auto& task : m_tasks)
        {
            if (task.id == taskId)
            {
                task.cancelled = true;
                break;
            }
        }
        m_cv.notify_one();
    }

    /// @brief 关闭定时器,取消所有任务并退出工作线程
    void shutdown()
    {
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_shutdown = true;
            m_cv.notify_one();
        }
        if (m_thread.joinable())
        {
            m_thread.join();
        }
    }

private:
    struct TimerTask
    {
        int                                   id;        // 任务唯一标识
        std::chrono::steady_clock::time_point time;      // 下次执行时间
        std::chrono::milliseconds             interval;  // 周期间隔;0 表示一次性任务
        std::function<void()>                 callback;  // 回调函数
        bool                                  cancelled; // 是否已被取消
    };

    std::mutex              m_mutex;
    std::condition_variable m_cv;
    bool                    m_shutdown; // 是否关闭定时器
    int                     m_nextId;   // 任务 id 生成器
    std::vector<TimerTask>  m_tasks;    // 保存所有任务
    std::thread             m_thread;

    /// @brief 后台线程函数,负责调度任务
    void worker()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        while (true)
        {
            // 若关闭且无任务,则退出线程
            if (m_shutdown && m_tasks.empty())
            {
                break;
            }

            // 若无任务,则等待新任务或关闭信号
            if (m_tasks.empty())
            {
                m_cv.wait(lock, [this] { return m_shutdown || !m_tasks.empty(); });
            }
            else
            {
                // 对任务按照下次执行时间排序(若任务数较多,可考虑更高效的数据结构)
                std::sort(m_tasks.begin(), m_tasks.end(), [](const TimerTask& a, const TimerTask& b) {
                    return a.time < b.time;
                });
                auto now = std::chrono::steady_clock::now();
                // 若最早的任务未到执行时间,则等待到该时间点
                if (m_tasks.front().time > now)
                {
                    m_cv.wait_until(lock, m_tasks.front().time, [this] {
                        return m_shutdown;
                    });
                }
            }

            if (m_shutdown)
            {
                // 退出前清除所有任务
                m_tasks.clear();
                break;
            }

            auto now = std::chrono::steady_clock::now();
            // 遍历所有到期任务进行处理
            for (auto it = m_tasks.begin(); it != m_tasks.end();)
            {
                if (it->time <= now)
                {
                    if (!it->cancelled)
                    {
                        // 为避免回调中阻塞调度线程,暂时取出回调函数,在解锁后执行
                        auto callback  = it->callback;
                        bool recurring = (it->interval.count() > 0);
                        if (recurring)
                        {
                            // 重新安排下次执行时间
                            it->time = now + it->interval;
                            // 解锁后执行任务,再重新加锁(也可以考虑异步执行)
                            lock.unlock();
                            callback();
                            lock.lock();
                            ++it;
                        }
                        else
                        {
                            // 一次性任务:解锁后执行任务,随后移除任务
                            lock.unlock();
                            callback();
                            lock.lock();
                            it = m_tasks.erase(it);
                        }
                    }
                    else
                    {
                        // 已取消的任务直接移除
                        it = m_tasks.erase(it);
                    }
                }
                else
                {
                    ++it;
                }
            }
        }
    }
};

#endif

main.cpp 使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main() {
    Timer timer;

    // 安排一个一次性任务,3 秒后执行
    int task1 = timer.schedule(std::chrono::milliseconds(3000), []() {
        std::cout << "One-shot task executed after 3 seconds." << std::endl;
    });

    // 安排一个周期性任务,初始 1 秒后开始,每 2 秒执行一次
    int task2 = timer.scheduleRecurring(std::chrono::milliseconds(1000), std::chrono::milliseconds(2000), []() {
        std::cout << "Recurring task executed." << std::endl;
    });

    // 主线程等待 7 秒
    std::this_thread::sleep_for(std::chrono::seconds(7));

    // 取消周期性任务
    timer.cancel(task2);
    std::cout << "Cancelled recurring task." << std::endl;

    // 等待 2 秒后关闭定时器
    std::this_thread::sleep_for(std::chrono::seconds(2));
    timer.shutdown();
    std::cout << "Timer shutdown." << std::endl;

    return 0;
}

参考

本文由作者按照 CC BY 4.0 进行授权