夜雨聆风学习资料网

ARTICLE · 1325942

读源码的第一站:从 steady_timer 走进 Asio

读源码的第一站:从 steady_timer 走进 Asio
📌 C++ 系列 · 第 36 篇  |  作者:舴艋舟点击上方「蓝字」关注,不错过系列更新

第 35 篇收官时我给过三条路,第一条是读源码,推荐入口是 Asio 的 steady_timer 那条线。

承诺要兑现。这一篇就讲这件事:为什么从它进,怎么读,读到什么程度算读完。

先把丑话说在前面。读源码不是逐行翻译,没有人逐行读完一个库。读源码是带着问题进去,找到答案出来。steady_timer 恰好是一个问题密度极高、代码量又极小的入口。

1. 为什么是 steady_timer

三个理由。

第一,它小。steady_timer 的头文件本体只有十几行,它是 basic_waitable_timer 的一个 typedef。你以为在读一个定时器,实际在读的是 Asio 的整个骨架,从类型组织到执行器到事件循环,全在这条线上。

第二,它没有网络。socket 那条线会把你拖进协议细节、缓冲区管理、错误处理的汪洋大海。定时器只有一个概念:在某个时刻做一件事。干扰最少。

第三,它离 io_context 最近。你在第 32 篇用过 co_await steady_timer 的 async_wait,现在去看它底下怎么实现,等于把你已经在用的东西拆开看齿轮。

2. 第一层:十几个字字的 typedef

打开 boost/asio/steady_timer.hpp,核心就一行。

typedef basic_waitable_timer<chrono::steady_clock> steady_timer;

steady_clock 是第 25 篇讲过的那个单调时钟,不受系统改时间影响。定时器用它是对的,墙钟会跳,单调钟不会。

所以 steady_timer 本身是个壳。真东西在 basic_waitable_timer.hpp。追进去。

3. 第二层:basic_waitable_timer 的四张脸

basic_waitable_timer 的类定义里,值得盯的是四个东西。

构造函数。它要一个 executor 或者 execution context,这直接回应了第 32 篇那个问题:为什么定时器都要跟 io_context 绑在一起。因为定时这件事最终要挂到某个执行环境的事件循环上,离开执行环境,时刻到了也没有人替你触发。

expires_after 和 expires_at。前者是相对时间,后者是绝对时间。注意它们每次调用会重置到期点,一个 timer 对象同一时刻只能挂一个等待。想并行多个等待,要么多个 timer,要么 basic_waitable_timer 的多路等待版本,先把前者用明白再说。

wait 和 async_wait。同步版会阻塞当前线程直到超时。异步版把一个 handler 挂进队列,立刻返回。读代码时重点看 async_wait,它是理解 Asio 的钥匙。

cancel。把挂着的等待全部取消,被取消的 handler 会带着 operation_aborted 错误码被回调。第 37 篇要讲的超时控制,就靠它。

4. 第三层:往下钻到 service

basic_waitable_timer 的 async_wait 里有一行核心调用,把实现转发给了 service。追这行调用,你会掉进 detail 目录,见到 deadline_timer_service 和 wait_op。

这一层是 Asio 真正的发动机,也是最值得花时间的地方。

看三样东西。任务怎么排队:timer 的等待操作被包装成 wait_op,按到期时间放进一个优先队列,最早到期的排最前面。事件循环怎么知道该睡多久:io_context 的 run() 在没有即时任务时,拿队首的到期时间算出最近超时,带着这个超时去等底层多路复用。时刻到了谁去触发:时间检查通过后,op 被投递回执行器,最终你的 handler 在它该在的线程上被调用。

看到这里,第 32 篇那些结论就全活了。io_context 为什么必须有 run(),因为有人得驱动这个循环。回调为什么带 error_code,因为取消和超时是两种命运,得区分。为什么需要 strand,因为同一个 timer 的回调可能被不同线程投递,你的状态需要保护。

5. 怎么读:三条纪律

第一,从用过的代码读起。先写一个 async_wait 的 demo 跑通,再进源码。带着运行印象读,每行代码都有画面。

第二,只追一条线。从 async_wait 进,沿调用链走到 op 入队为止,别半路去研究 reactor 的多路复用。那是另一个下午的事。

第三,读完要动手验证。改一个小东西,比如给等待加一个计数,或者把取消的路径手动走一遍,在 handler 里打印 error_code。能改,才算读到。

读完这一条线,Asio 在你眼里就从一个黑盒子变成一堆咬合的齿轮。这个感觉,是你后面读任何库的通用能力。

6. 最后

第 35 篇说,读到能改一点小东西为止。这篇把标准再具体一点:你能说清 async_wait 之后、handler 被调用之前,中间发生了什么,就算过关。

要是这篇帮你进了源码的门,点个在看,或者转发给那个说想读框架源码却一直没敢开工的朋友。

下一篇走第二条路,把第 32 篇那个 echo server 做成能用的东西,先解决两个要命的细节:超时和优雅退出。

江湖路远,我们下一篇见。

· · ·
觉得这篇有用,点个「在看」,或转发给还在跟 C++ 搏斗的朋友。关注「舴艋舟」,C++ 系列持续更新,不迷路。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料