news 2026/7/23 5:53:00

C++计时器实现:从阻塞到多线程,掌握高精度定时器设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++计时器实现:从阻塞到多线程,掌握高精度定时器设计

1. 项目概述:从“需求”到“实现”的思考路径

最近在整理一些C++的练手项目,发现“计时器/倒计时”这个需求出现的频率相当高。无论是准备面试时被问到“如何实现一个简单的计时器”,还是在实际开发中需要为某个功能模块添加超时控制,甚至是写个小游戏需要倒计时功能,这个看似简单的功能背后,其实藏着不少值得琢磨的细节。很多人一上来就开始写while循环加sleep,结果不是精度不够,就是界面卡死,或者线程管理一团糟。今天,我就结合自己踩过的坑,聊聊怎么用C++实现一个健壮、可用的计时器,特别是倒计时功能。我们会从最基础的阻塞式倒计时讲起,逐步深入到多线程、高精度计时以及面向对象的设计,目标是让你不仅能写出代码,更能理解每种方案背后的权衡与选择。

2. 核心需求解析与方案选型

2.1 功能边界与核心挑战

一个计时器,尤其是倒计时器,核心功能就一句话:在指定的时间长度后,触发一个动作或通知。但拆开来看,我们需要考虑几个层面:

  1. 时间度量:我们用什么来度量时间?是简单的秒,还是需要毫秒、微秒级精度?
  2. 触发机制:时间到了之后,如何通知程序的其他部分?是直接执行一个函数(回调),还是设置一个标志位,或者发送一个消息/事件?
  3. 阻塞与非阻塞:计时过程是否会“卡住”当前线程?这对于有用户界面或需要同时处理其他任务的应用至关重要。
  4. 精度与误差:我们实现的计时器到底有多准?系统调度、循环本身的开销都会带来误差。
  5. 资源与生命周期:计时器对象如何创建、启动、停止和销毁?特别是多计时器场景下的管理。

对于C++来说,我们手头的工具主要来自标准库:<chrono>库提供了现代、类型安全的时间工具;<thread>库允许我们创建并发;<functional>和Lambda表达式则方便我们定义到期时要执行的动作。

2.2 三种典型实现方案对比

在动手前,我们先在脑子里过几种常见的路子,分析一下它们的适用场景和坑。

方案一:忙等待循环 (Busy Wait Loop)这是最直觉的方法:在一个循环里不断检查当前时间是否超过截止时间。

auto start = std::chrono::steady_clock::now(); auto duration = std::chrono::seconds(10); while (std::chrono::steady_clock::now() - start < duration) { // 循环空转,或者做一些非常轻量的检查 } // 时间到!
  • 优点:概念简单,理论上精度可以很高(取决于检查频率)。
  • 缺点:CPU占用率100%(忙等待),完全阻塞当前线程,几乎不实用。除非在裸机或无操作系统的嵌入式环境做极简实现。

方案二:阻塞式延时 (Blocking Delay)利用std::this_thread::sleep_for让当前线程睡眠指定时间。

std::this_thread::sleep_for(std::chrono::seconds(10)); // 睡眠结束后继续执行
  • 优点:简单,不忙等待,节省CPU。
  • 缺点:整个线程被挂起,期间不能做任何其他事情。对于需要响应用户输入或处理网络请求的主线程来说,这是灾难性的。

方案三:基于线程的非阻塞计时器 (Thread-based Timer)这是最实用、最经典的方案。我们创建一个独立的线程来负责睡眠和触发回调。主线程或其他线程可以继续运行。

void startTimer(int seconds, std::function<void()> callback) { std::thread([seconds, callback]() { std::this_thread::sleep_for(std::chrono::seconds(seconds)); callback(); }).detach(); // 分离线程,让其独立运行 }
  • 优点:非阻塞,主流程不受影响。灵活性高。
  • 缺点:涉及线程创建、销毁开销。回调函数在子线程中执行,需要注意线程安全问题(如访问共享数据)。大量计时器会创建大量线程,消耗系统资源。

对于大多数应用场景,方案三(基于线程的非阻塞计时器)是起点。我们的任务就是在这个基础上,把它做得更健壮、更易用、更高效。比如,如何避免频繁创建销毁线程?如何管理多个计时器?如何提高精度?这就是我们后面要深入的内容。

注意:在图形界面(如Qt、MFC)或游戏引擎(如Unity C++部分)中,通常推荐使用其内置的定时器机制,因为它们与消息循环深度集成,更安全、更高效。我们这里讨论的是标准C++下的通用实现。

3. 基础实现:一个简单的单次倒计时器

让我们先从地基开始,实现一个最简单的、一次性的倒计时器。这个版本会暴露所有基础问题,正是我们学习和改进的素材。

3.1 使用std::threadstd::function的雏形

我们的目标是:创建一个Timer类,给它一个时间间隔和一个回调函数,调用start()后,它能在指定时间后异步执行回调。

// Timer_v1.h #include <chrono> #include <functional> #include <thread> #include <atomic> class Timer { public: Timer() : m_expired(true), m_tryToExpire(false) {} ~Timer() { stop(); } void start(int interval, std::function<void()> task) { // 如果计时器已经在运行,先停止旧的 if (!m_expired.load()) return; // 启动新线程 m_expired = false; std::thread([this, interval, task]() { // 线程函数:睡眠,然后执行任务 while (!m_tryToExpire.load() && interval > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(interval)); if (!m_tryToExpire.load()) { task(); } } // 任务执行完毕或被打断,标记为过期 { std::lock_guard<std::mutex> locker(m_mutex); m_expired = true; m_expiredCond.notify_one(); } }).detach(); // 分离线程,让它在后台运行 } void stop() { // 如果已经过期,直接返回 if (m_expired.load()) return; // 尝试让计时器线程退出 if (m_tryToExpire.exchange(true)) { return; // 已经在退出了 } // 等待计时器线程真正结束 { std::unique_lock<std::mutex> locker(m_mutex); m_expiredCond.wait(locker, [this] { return m_expired.load(); }); } m_tryToExpire = false; // 重置状态,以便下次启动 } private: std::atomic<bool> m_expired; // 计时器是否已过期/停止 std::atomic<bool> m_tryToExpire; // 外部是否请求停止 std::mutex m_mutex; std::condition_variable m_expiredCond; };

代码拆解与初版问题:

  1. start方法:接收间隔时间(毫秒)和一个std::function<void()>类型的任务。它首先检查计时器是否已在运行(通过m_expired标志)。然后,它启动一个分离的线程。这个线程的核心是一个while循环,条件是“没有外部请求停止”且“间隔大于0”。在循环内,它睡眠指定的间隔,然后执行任务。注意,这里实现的是一个周期性定时器,而不是单次倒计时。这是我们第一个需要修改的点。
  2. stop方法:用于安全地停止计时器。它设置m_tryToExpire标志,通知工作线程退出。然后通过条件变量m_expiredCond等待工作线程确认退出(将m_expired设为true)。这避免了直接detach后线程变成“野线程”无法管理的局面。
  3. 线程安全:使用了std::atomic<bool>来保证对布尔标志的读写是原子的。使用std::mutexstd::condition_variable来同步线程状态。这是多线程编程的基础。
  4. 第一个大问题:这个Timer周期性的!它会在每个interval后重复执行任务。而我们的标题要求是“倒计时”,通常指单次触发。所以我们需要区分“单次触发”和“周期性触发”两种模式。

3.2 修正:实现真正的单次倒计时

修改思路:移除while循环,线程只睡眠一次,执行一次任务,然后结束。

// Timer_v2.h (部分修改) void startOnce(int afterMs, std::function<void()> task) { if (!m_expired.load()) return; // 防止重复启动 m_expired = false; m_tryToExpire = false; std::thread([this, afterMs, task]() { // 单次睡眠 if (afterMs > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(afterMs)); } // 检查是否被中途停止 if (!m_tryToExpire.load()) { task(); // 执行回调 } // 任务完成,标记过期 { std::lock_guard<std::mutex> locker(m_mutex); m_expired = true; m_expiredCond.notify_one(); } }).detach(); }

同时,我们可以保留一个startPeriodic方法用于周期性任务。这样,我们的类就具备了两种能力。在stop方法中,我们需要判断:如果是单次计时器,可能线程正在睡眠,我们通过设置m_tryToExpire来阻止它醒来后执行回调;如果是周期性计时器,则跳出循环。

实测与第一个坑:线程生命周期这个版本已经可以工作了。但你很快会发现一个问题:每次调用startOncestartPeriodic,即使是对同一个Timer对象,也会创建一个新的线程。如果频繁启动/停止,线程的创建和销毁会成为性能瓶颈。此外,detach出去的线程,其生命周期与Timer对象无关,如果Timer对象析构了,但它的线程还在运行并访问成员变量(比如m_tryToExpire),就会导致未定义行为(悬空引用)。虽然我们的stop和析构函数尝试等待线程结束,但在复杂场景下仍可能出错。

实操心得一:慎用detach除非你非常清楚线程的生命周期,并且确保线程不会访问可能失效的对象成员,否则尽量使用join,让线程的生命周期与对象绑定。对于计时器这种典型场景,更好的模式是:在Timer类内部持有一个std::thread对象成员,在start时构造它,在stop或析构时join它。这保证了线程资源被正确管理。

4. 进阶设计:可管理、高精度的计时器类

基于上面的教训,我们重新设计。目标是:一个线程只服务于一个计时任务,生命周期绑定,支持单次和周期模式,并且提供更高的精度控制。

4.1 改进的类设计与管理策略

我们不再每次start都创建新线程,而是让Timer对象在构造时就启动一个“工作线程”,这个线程大部分时间在等待(睡眠)。当有计时任务提交时,我们通过条件变量通知它。这个工作线程维护一个“任务队列”或“最近触发时间”,不断检查并执行到期的任务。这实际上是单线程事件循环模型,也是很多定时器库的核心思想。

但为了简化,我们先实现一个折中方案:每个Timer对象仍然对应一个后台线程,但这个线程在Timer对象整个生命周期内都存在,通过状态标志来控制其行为。这避免了频繁创建线程的开销。

// Timer_v3.h #include <chrono> #include <functional> #include <thread> #include <atomic> #include <mutex> #include <condition_variable> class Timer { public: enum class TimerType { ONCE, PERIODIC }; Timer() : m_stop(false) { // 构造函数中直接启动工作线程 m_worker = std::thread(&Timer::run, this); } ~Timer() { { std::lock_guard<std::mutex> lock(m_mutex); m_stop = true; m_cond.notify_one(); // 通知线程醒来检查退出条件 } if (m_worker.joinable()) { m_worker.join(); } } void start(TimerType type, int intervalMs, std::function<void()> task) { { std::lock_guard<std::mutex> lock(m_mutex); m_type = type; m_interval = std::chrono::milliseconds(intervalMs); m_task = std::move(task); m_running = true; m_wakeupTime = std::chrono::steady_clock::now() + m_interval; m_cond.notify_one(); // 通知工作线程开始计时 } } void stop() { { std::lock_guard<std::mutex> lock(m_mutex); m_running = false; // 不清空任务,但设置不运行,线程会在醒来后跳过执行 m_cond.notify_one(); // 唤醒线程以检查新状态 } } private: void run() { while (true) { std::unique_lock<std::mutex> lock(m_mutex); // 等待条件:1. 被stop唤醒;2. 计时时间到 if (m_stop) { break; // 析构函数通知退出 } if (!m_running) { // 没有任务,线程休眠直到被start或stop唤醒 m_cond.wait(lock); continue; } // 计算还需要等待多久 auto now = std::chrono::steady_clock::now(); if (now < m_wakeupTime) { // 时间未到,休眠到预定时间 m_cond.wait_until(lock, m_wakeupTime); // 醒来后,需要重新检查状态(可能被stop提前唤醒) continue; } // 时间到!执行任务 if (m_task) { // 注意:在锁内执行任务是非常糟糕的做法,可能导致死锁或性能问题! // 正确做法是释放锁后再执行。 lock.unlock(); m_task(); lock.lock(); } // 任务执行完毕,根据模式决定下一步 if (m_type == TimerType::PERIODIC && m_running) { // 周期性任务,重新计算下一次唤醒时间 m_wakeupTime += m_interval; // 继续循环,等待下一次执行 } else { // 单次任务,执行一次后结束 m_running = false; // 线程将进入休眠,等待下一次start调用 } } } std::thread m_worker; std::atomic<bool> m_stop; // 控制整个工作线程退出 std::mutex m_mutex; std::condition_variable m_cond; TimerType m_type{TimerType::ONCE}; std::chrono::milliseconds m_interval{0}; std::function<void()> m_task; bool m_running{false}; std::chrono::steady_clock::time_point m_wakeupTime; };

设计要点解析:

  1. 常驻工作线程:在构造函数中启动线程,执行run方法。析构函数通过m_stop标志和条件变量通知线程退出,并调用join。这确保了线程资源安全释放。
  2. 状态驱动:工作线程的核心是一个while循环,其行为由m_runningm_stopm_wakeupTime等状态控制。没有任务时,线程在m_cond.wait(lock)处休眠,不消耗CPU。
  3. 精准唤醒:使用std::condition_variable::wait_until,让线程休眠到精确的唤醒时间点,而不是循环检查,这大大减少了不必要的CPU唤醒和上下文切换。
  4. 锁与任务执行:这是一个关键点。在run函数中,当判断时间到了需要执行任务时,我们先解锁(lock.unlock()),再执行m_task(),执行完再重新上锁(lock.lock())。绝对不要在持有锁的情况下执行用户回调!因为用户回调可能执行任意长时间的操作,或者它内部可能尝试获取其他锁,这极易导致死锁或严重降低并发性能。
  5. 单次与周期模式:通过m_type区分。单次任务执行后,将m_running设为false;周期性任务则更新下一次的m_wakeupTime,继续循环。

这个版本已经是一个功能比较完整、健壮性较高的计时器了。但它仍然有一个潜在问题:精度std::this_thread::sleep_forcondition_variable::wait_until的精度受操作系统调度器影响,在Windows或负载较重的Linux系统上,可能会有几毫秒到几十毫秒的延迟。对于需要高精度计时(如游戏帧同步、音视频处理)的场景,这不够。

4.2 精度提升:认识std::chrono与时钟源

C++11的<chrono>库提供了三种时钟:

  • std::chrono::system_clock:系统时钟,可调节(可能被NTP或用户修改),适合表示“墙上时间”。
  • std::chrono::steady_clock:单调时钟,保证从不递减,且调整速率相对稳定。这是测量时间间隔和计时的最佳选择,我们一直在用它。
  • std::chrono::high_resolution_clock:可能是system_clocksteady_clock的别名,提供最高精度的计时,但不一定是单调的。通常它就是steady_clock

精度问题主要不在时钟源,而在睡眠函数的唤醒延迟sleep_forwait_until只是向操作系统发出一个“至少睡眠这么久”的请求,操作系统会在其调度周期内唤醒线程,这个周期就是“时间片”,在典型桌面操作系统中可能是1ms到15ms。

如何提高精度?对于要求极高的场景(例如,每16.67ms触发一次的60fps游戏循环),纯睡眠是不可靠的。常见的做法是“忙等待+短睡眠”结合:

  1. 在接近目标时间时(比如还剩几毫秒),使用短间隔的sleep_for(例如1ms)。
  2. 当非常接近时(例如还剩几百微秒),切换到忙等待循环,不断检查时间。
  3. 这需要在精度和CPU占用之间做权衡。
// 高精度等待函数的示例 void preciseSleepUntil(std::chrono::steady_clock::time_point wakeupTime) { while (true) { auto now = std::chrono::steady_clock::now(); auto remaining = wakeupTime - now; if (remaining <= std::chrono::milliseconds(0)) { break; // 时间到 } if (remaining > std::chrono::milliseconds(2)) { // 剩余时间较长,使用睡眠 std::this_thread::sleep_for(std::chrono::milliseconds(1)); } else { // 剩余时间很短,忙等待以获得更高精度 // 这里可以插入一个CPU暂停指令(如_mm_pause)来减少功耗和总线冲突 // 但为了可移植性,我们只是空循环 std::this_thread::yield(); // 建议让出时间片,避免完全饿死其他线程 } } }

然后,在run函数中,将m_cond.wait_until(lock, m_wakeupTime)替换为preciseSleepUntil(m_wakeupTime)(注意锁的管理,需要先释放锁再调用这个函数)。不过,对于绝大多数应用,wait_until的精度已经足够,盲目追求纳秒级精度往往会带来不必要的CPU消耗。

实操心得二:时间测量的正确姿势

  1. 始终使用std::chrono::steady_clock来测量时间间隔和计算超时。system_clock可能会因为闰秒、时间同步等原因“跳变”。
  2. 理解“睡眠”的语义sleep_for(duration)的意思是“让当前线程睡眠至少duration这么久”,而不是“精确睡眠duration”。操作系统保证的最短睡眠时间通常等于其调度器的“滴答”间隔。
  3. 如果需要测量一段代码的执行时间,使用steady_clocknow()函数在开始和结束各取一次点,然后相减。避免使用clock()函数,它在多核环境下可能不准确。

5. 多计时器管理与高级话题

单个计时器好办,但如果你的程序需要管理成百上千个定时任务呢?比如一个网络服务器,需要管理成千上万的连接超时。为每个任务都创建一个Timer对象(及其背后的线程)是极其低效的。

5.1 时间轮与最小堆:高效管理海量定时器

这时就需要一个中心化的定时器管理器。常见的两种数据结构是时间轮最小堆

最小堆 (Min-Heap)将所有定时任务按照触发时间排序,放入一个优先队列(最小堆)中。管理器只有一个线程,它不断检查堆顶元素(即最近要触发的任务)的触发时间。如果时间没到,就睡眠到那个时间点;如果时间到了,就取出执行,然后重新检查堆顶。

  • 优点:实现相对简单,在定时器数量不多时效率很高。C++中可以用std::priority_queue配合自定义比较函数实现。
  • 缺点:插入和删除定时器的复杂度是O(log n)。当定时器数量巨大时,每次调整堆都有开销。

时间轮 (Timing Wheel)像一个表盘,分为多个槽(slot),每个槽代表一个时间间隔。一个指针按固定频率前进。每个槽里挂着一个链表,存放在该时间间隔内要触发的所有任务。当指针指向某个槽时,就执行该槽链表中的所有任务。

  • 优点:添加和删除定时器的复杂度接近O(1)。特别适合对性能要求极高的场景,如Linux内核的定时器实现。
  • 缺点:实现复杂,且精度受“槽”的粒度限制。如果定时范围很大(从毫秒到天),需要多层时间轮(类似时分秒)。

对于大多数C++应用,如果定时器数量在几百几千这个量级,使用基于std::multimapstd::priority_queue的单线程管理器已经足够。下面是一个极简的示例框架:

class TimerManager { public: using TimePoint = std::chrono::steady_clock::time_point; using TimerId = uint64_t; TimerId addTimer(TimePoint when, std::function<void()> task) { std::lock_guard<std::mutex> lock(m_mutex); TimerId id = ++m_nextId; m_timers.emplace(when, std::make_pair(id, std::move(task))); m_cond.notify_one(); // 通知工作线程可能有更早的任务了 return id; } bool cancelTimer(TimerId id) { std::lock_guard<std::mutex> lock(m_mutex); // 需要遍历查找,这是最小堆方案的缺点之一。可以用额外map存储id->iterator来优化。 for (auto it = m_timers.begin(); it != m_timers.end(); ++it) { if (it->second.first == id) { m_timers.erase(it); return true; } } return false; } void run() { while (!m_stop) { std::unique_lock<std::mutex> lock(m_mutex); if (m_timers.empty()) { m_cond.wait(lock); continue; } auto& [when, taskPair] = *m_timers.begin(); auto& [id, task] = taskPair; auto now = std::chrono::steady_clock::now(); if (now >= when) { // 时间到,执行任务 m_timers.erase(m_timers.begin()); lock.unlock(); task(); // 执行任务 lock.lock(); } else { // 等待直到最近的任务触发时间 m_cond.wait_until(lock, when); } } } private: std::mutex m_mutex; std::condition_variable m_cond; std::multimap<TimePoint, std::pair<TimerId, std::function<void()>>> m_timers; std::atomic<bool> m_stop{false}; std::atomic<TimerId> m_nextId{0}; };

这个管理器在一个独立线程中运行run函数,管理所有定时任务。addTimer负责添加,cancelTimer负责取消(这里实现的是低效的线性查找,实际应用需要优化)。

5.2 线程安全与回调设计

线程安全:我们的TimerManager使用了互斥锁保护m_timers容器。注意,在run函数中执行用户任务task()时,我们释放了锁。这是必须遵守的原则。

回调设计:我们使用了std::function<void()>。这是一个非常灵活的选择,可以绑定全局函数、成员函数、lambda表达式等。但需要注意,如果回调捕获了局部变量的引用或指针,必须确保在回调执行时,这些变量仍然有效(生命周期问题)。对于类成员函数,通常使用std::bind或lambda来绑定this指针,但要小心计时器生命周期长于对象生命周期时导致的野指针问题。一种常见做法是让对象持有计时器的weak_ptr,或者在对象析构时主动取消所有关联的计时器。

6. 常见问题、调试技巧与性能考量

6.1 编译与环境配置问题

很多人,尤其是初学者,在写C++多线程程序时遇到的第一个问题不是逻辑错误,而是编译不过。

  • 找不到头文件:确保你的编译器支持C++11或更高版本。<chrono>,<thread>,<mutex>,<atomic>,<condition_variable>都是C++11标准库的一部分。
    • GCC/G++: 编译时加上-std=c++11(或-std=c++14,-std=c++17)。
    • Clang: 同上。
    • MSVC (Visual Studio): 项目属性 -> C/C++ -> 语言 -> C++语言标准,选择“ISO C++17 标准”或更高。VS2015及以上版本默认支持足够好的C++11/14。
  • 链接错误:在Linux/macOS下使用GCC编译多线程程序,需要链接pthread库。在编译命令末尾加上-lpthread
    g++ -std=c++17 -o my_timer my_timer.cpp -lpthread
  • 在IDE中(如VSCode、CLion):确保你的CMakeLists.txt或编译配置中正确设置了C++标准并链接了线程库。对于VSCode,tasks.json文件中的args需要包含-std=c++17-lpthread

6.2 运行时典型问题排查

  1. 程序不退出/卡在析构函数:这是最常见的问题。通常是工作线程没有正确退出。
    • 检查点:确保你的while循环退出条件能被触发。在Timer的析构函数中,是否先设置了停止标志(如m_stop=true),然后通知了条件变量(m_cond.notify_one()),最后调用了join()?顺序很重要。
    • 死锁:检查是否在持有锁的情况下调用了可能阻塞的操作(如执行用户回调、等待另一个锁)。永远不要在锁内执行未知的用户代码。
  2. 回调函数没有被执行
    • 检查计时器是否真的start了,并且interval大于0。
    • 检查是否在回调执行前调用了stop,导致m_running被设为false
    • 如果是周期性任务,检查一次执行后,是否正确地重新设置了m_wakeupTime并进入了下一轮等待。
  3. 精度远远达不到预期
    • 首先确认你用的是std::chrono::steady_clock
    • 在Linux下,可以尝试提高线程的调度优先级和设置实时调度策略(需要root权限,使用sched_setscheduler),但这通常用于特定实时系统,普通应用慎用。
    • 考虑系统负载。如果CPU非常繁忙,线程可能无法被及时调度。
  4. 多计时器竞争与回调顺序:如果你有多个几乎同时到期的计时器,它们的回调执行顺序是不确定的,取决于操作系统的线程调度。如果你需要严格的顺序,要么使用单线程的TimerManager,要么在回调函数内部通过锁等机制进行同步。

6.3 性能考量与优化建议

  • 线程数量:避免创建大量线程。每个线程都有独立的内核栈(通常几MB)和上下文切换开销。对于定时任务,使用一个或少数几个管理器线程是更好的选择。
  • 锁的粒度:尽量减少持锁时间。在TimerManager的示例中,我们只在操作容器(m_timers)时才持有锁,执行任务前就释放了。
  • 内存分配:频繁地创建std::function对象可能会有动态内存分配开销。如果性能敏感,可以考虑使用自定义的可调用对象,或者对象池来复用。
  • 计时器数量:当定时任务数量达到万级以上时,需要认真选择数据结构。时间轮在大量定时器场景下通常表现优于最小堆。
  • 回调函数的性能:计时器回调函数应该尽可能快地执行完毕。如果回调需要做繁重的I/O或计算,应该考虑将其投递到另一个专门的线程池中去执行,避免阻塞计时器管理线程,影响其他定时任务的精度。

实现一个工业级的计时器是复杂的,涉及到并发控制、数据结构、系统编程等多方面知识。我们从最简单的sleep开始,逐步引入了线程、同步原语、条件变量、时间处理,最后探讨了管理多个计时器的思路。希望这个逐步深入的过程,能帮助你不仅写出可用的代码,更能理解每一行代码背后的权衡与设计哲学。在实际项目中,如果条件允许,我通常会优先考虑使用成熟的网络库(如Boost.Asio、libevent)或框架提供的定时器组件,它们经过了广泛的测试和优化。但自己动手实现一遍,无疑是理解其原理的最佳途径。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 5:50:27

Profinet转EtherCAT网关连接禾川伺服驱动器电机配置案例

Profinet转EtherCAT网关连接禾川伺服驱动器电机硬件连接&#xff0c;设备清单&#xff1a;Profinet主站&#xff08;如西门子PLC S7-1200/1500&#xff09;、小疆智控Profinet转EtherCAT网关、禾川伺服驱动器&#xff08;支持EtherCAT从站协议&#xff09;、网线、24V电源步骤1…

作者头像 李华
网站建设 2026/7/23 5:50:10

2026年ALM工具哪个好用?8款主流产品对比与选型指南

2026年值得关注的ALM工具包括ONES、Siemens Polarion ALM、IBM Engineering Lifecycle Management、PTC Codebeamer、Jama Connect、Perforce ALM、Azure DevOps和OpenText Application Quality Management。这几款产品各有侧重。有的擅长复杂需求、基线和审计&#xff0c;有的…

作者头像 李华
网站建设 2026/7/23 5:46:15

C语言基础学习(数据类型)

C编程语言执行效率高可以控制操作硬件资源开销小跨平台可移植性强 行业基础C语言学习内容&#xff1a;基本数据类型表达式及运算符常用输入输出函数流程控制&#xff08;分支结构.循环结构&#xff09;函数数组指针构造数据类型内存管理 位运算程序调试方法C语言学前储备知识计…

作者头像 李华
网站建设 2026/7/23 5:43:35

深入解析Stellaris uDMA控制器:从基础到高级应用实战

1. 项目概述&#xff1a;为什么我们需要uDMA&#xff1f;在嵌入式开发&#xff0c;尤其是基于ARM Cortex-M3这类资源受限的微控制器项目中&#xff0c;我们常常面临一个核心矛盾&#xff1a;CPU的算力是宝贵的&#xff0c;但数据搬运却是频繁且耗时的。想象一下&#xff0c;你的…

作者头像 李华
网站建设 2026/7/23 5:43:14

C++模板进阶:从类型推导到元编程实战

1. 项目概述&#xff1a;从“能用”到“精通”的C模板之路如果你写过一些C代码&#xff0c;用过std::vector<int>或者自己写过简单的template <typename T> T max(T a, T b)&#xff0c;那你已经踏入了模板世界的大门。但很多时候&#xff0c;我们仅仅停留在“知道…

作者头像 李华