%% # 纲要 > 主干纲要、Hint/线索/路标 # Q&A #### 已明确 #### 待明确 > 当下仍存有的疑惑 **❓<font color="#c0504d"> 有什么问题?</font>** %% # 时钟相关 API 总结 时钟相关的 API 有三类: - **C 时钟库 `<time.h>`**:适合简单的时间操作,如记录时间戳和时间差。 - **POSIX 时钟库 `<sys/time.h>`**:提供更高精度和更丰富的时间功能,但依赖于POSIX系统。 - **C++ 时钟库 `<chrono>`**:提供类型安全、现代化、跨平台的时间操作,是现代C++开发的首选。 | | C | POSIX | C++ | | ---- | ----------------------------------------------- | ----------------------------------------------------------- | ---------- | | 头文件 | `<time.h>` | `<sys/time.h>` | `<chrono>` | | 时间单位 | 秒 | 微秒、纳秒 | 任意(秒 ~ 纳秒) | | 精度 | 秒级 | 纳秒级 | 高精度 | | 相关操作 | 基本时间处理 | 复杂操作 | 复杂操作 | | 数据结构 | - `time_t`(存储时间戳) <br>- `struct tm`(存储时间信息各字段) | - `struct timeval` (存储秒和微秒)<br>- `struct timespec` (存储秒和纳秒) | | ![[02-开发笔记/99-Unix 环境编程/时钟 API#^ibx7pi]] <br> # C 标准库的时钟 API > 参见[^1] [^2] ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-E8D1E70E9A06AD5332712BEABE21EAE8.png|537]] > [!NOTE] `struct tm`、`time_t`、字符串之间的转换 > > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-1EBC1E0FE690C5F46FE723E884ED9C06.png|423]] > > [!NOTE] `struct_tm` 结构 > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-4DF4BECC7E48D97FCB747E9F21C6AB96.png|417]] #### 使用示例 C 标准库提供的 API 主要有两个类型:`time_t` 和 `struct tm` 。 使用时,先获取 `time_t t = time(NULL)`, 再将 `time_t` 转换成 `struct tm` 类型,然后**可通过 `strftime()` 按格式输出**。 ```c #include <time.h> #include <stdio.h> #include <unistd.h> // 打印 `struct tm` 结构体中各个字段值 void printf_struct_tm(struct tm* time) { printf("Local time: %d-%02d-%02d %02d:%02d:%02d\n", time->tm_year + 1900, // Year-1900 time->tm_mon + 1, // [0-11] time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); } // 获取时间 void get_time_exam() { // 获取当前时间戳. (自1970/1/1 00:00:00以来的"累积秒数") time_t t = time(NULL); printf("%ld\n", (long)t); // 将时间戳转为本地时间 (time_t => struct tm) struct tm *local_tm = localtime(&t); printf_struct_tm(local_tm); // 转换: struct tm =》 time_t time_t t2 = mktime(local_tm); printf("%ld\n", (long)t2); // 将time_t输出为固定格式(不可改)字符串 char *p = ctime(&t2); printf("%s", p); // 输出: Sat Nov 9 20:16:34 2024 // 将`struct tm`结构体按指定格式转为 "字符串": char buffer[80]; strftime(buffer, sizeof(buffer), "Date: %Y-%m-%d %H:%M:%S", local_tm); printf("%s\n", buffer); // 解析字符串转为`struct tm`结构. const char* time_str = "2024-11-09 14:30:45"; struct tm time; if (strptime(time_str, "%Y-%m-%d %H:%M:%S", &time)) { printf_struct_tm(&time); } else { printf("Failed to parse time.\n"); } } // 计算时间差 void diff_time_exam() { time_t start_t = time(NULL); sleep(3); time_t end_t = time(NULL); // `difftime()` 返回时间差(秒) double elapsed = difftime(end_t, start_t); printf("Elapsed time: %.2f seconds\n", elapsed); } int main() { get_time_exam(); diff_time_exam(); // 获取程序运行时间(CPU时间, 单位是 CLOCKS_PER_SECOND, 即每秒的滴答数. clock_t ct = clock(); } ``` <br><br> # POSXI 库中的时钟 API > 参见[^2] ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-6F9EC9305C66E13A2E27294AF181D4AB.png|505]] > [!NOTE] 各时间函数、类型之间的转换关系 > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-7C5C6F91F05D928FF73180BB6F616968.png|535]] ^ibx7pi <br><br> # C++ 标准库的时钟 API > 由头文件 `<chrono>` 提供 ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-74AE84355BB4BDB29906C8A623BCD6CE.png|469]] ## 接口总结 C++ 以 "**面向对象**" 的方式提供了 "时间" 相关处理 [^3][^4],各个类可分为三部分: - **时钟类 Clock**:提供了获取 "**当前时间点**" 的接口 - **时间点类 Time Point** :表示一个具体时间点 - **`std::chrono::time_point<ClockType, Duration>` 类模版** - **时长类 Duration**:表示时间间隔,例如多少秒,多少毫秒; - **`std::chrono::duration<Rep, Period>` 类模版** ## 时钟类 时钟类包括以下几种: | 时钟类 | 说明 | | -------------------------------------- | ------------------------------------------ | | `std::chrono::system_clock` 类 | **当前系统时钟**(非单调,受系统设置时间的影响) | | `std::chrono::steady_clock` 类 | **单调时钟** (自**系统启动以来从 0 起单调递增**,适用于测量时间间隔) | | `std::chrono::high_resolution_clock` 类 | **高精度时钟类** (编译器通常实现为上述两个时钟之一的别名) | 每个时钟类都提供了 **静态成员函数 `now()`**:获取 "**当前时间点**",返回**时间点类实例**。 > [!caution] `now()` 返回的时间点类,其 `duration` 的滴答间隔可能是纳秒,而非秒! > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-90719985B422E8D85F376F1AA25C609F.png|648]] > [!NOTE] `system_clock` 额外提供了两个**静态成员函数**,实现 `time_t` 与 `time_point` 类之间的转换: > > - `to_time_t()`: 将 `std::chroro::time_point` 类实例转为 `time_t` 类型(`<ctime>` 头文件中) > - `from_time_t()`:将 `time_t` 类型转为 `std::chrono::time_point` 类型 > #### 使用示例 ```cpp void get_time_exam() { // 获取当前时间点 auto now = std::chrono::system_clock::now(); // 转为time_t类型的秒数: std::chronor::time_point => std::time_t std::time_t now_t = std::chrono::system_clock::to_time_t(now); char buf[80]; strftime(buf, sizeof(buf), "Date: %Y-%m-%d %H:%M:%s", localtime(&now_t)); printf("%s\n", buf); // 逆转换: std::time_t => std::chrono::time_point auto now2 = std::chrono::system_clock::from_time_t(now_t); } /* 测量时间间隔. * 使用`std::chrono::steady_clock`单调时钟进行测量, 不受系统时间修改的影响. */ void diff_time_exam() { // 获取当前时间点 auto start = std::chrono::steady_clock::now(); // 休眠2秒 std::this_thread::sleep_for(std::chrono::seconds(2)); // 结束计时 auto end = std::chrono::steady_clock::now(); // 计算时间间隔 std::chrono::duration<double> elapsed = end - start; cout << "Elapsed time::" << elapsed.count() << " seconds\n"; } int main(int argc, char** argv) { get_time_exam(); diff_time_exam(); return 0; } ``` <br> ## 时间点类 时间点由实例化的 "**模版类**" 表示:`std::chrono::time_point<ClockType, Duration>`,其中: - `ClockType` 为 **时钟类型**(上述的`system_clock` 或 `steady_clock`), - `Duration` 为 **时长类** 时间点类的实例 `time_point` 存储着 "**某一时间点==相较于纪元时间的时间间隔==(秒数**)"。 > [!NOTE] 纪元时间(epoch) > > - UNIX/Linux 的时间纪元: `1970/1/1 00:00:00`,以 "秒" 为单位度量。 > - Windows 的时间纪元:`1601/1/1 00:00:00`,以 "纳秒 "为单位度量。 #### "时间点类" 与 "时长类" 之间的算术运算 ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-3CB2B266CAE27D5EA7450A8530DF86F1.png|646]] <br> ## 时长类 **时长类** 用于表示时间间隔或持续时间,由 **实例化的模板类 `std::chrono::duration<Rep, Period>`** 表示。 其中 `Rep` 与 `Period` 两个模版参数含义: - `Rep`:表示 "**==滴答数==**"(以 "**滴答次数**" 衡量时间长度),实际类型通常为算数类型,例如 `int64_t`; - `Period` 表示 "**==滴答周期==**"(两次**滴答之间间隔的秒数**),由**编译器常量类 `ratio` 指定** - (未指定时默认滴答周期为 1s,即 `ratio<1>`) > [!note] 时长类通过 `滴答数 * 滴答周期` 来表示时长,由滴答周期指定了 "**精度**"。 > > `std::chrono` 命名空间下为一系列**时长模版类**定义了别名,例如 `std::chronor::seconds`: > > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-6DA1E16DA68ABB3C78652EBEE3B033CC.png|456]] > > 其中 `milli` 是`ratio<1, 1000>` 别名,表示**毫秒精度**; `micro` 是 `ratio<1, 1000000>` 别名,表示**微秒精度**。 #### 成员函数 ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-70701175ECFE23581AB639486D0B29CD.png|803]] #### 使用说明 ```cpp void create_duration() { // 构建了一个3s的时长类实例. duration<int64_t, ratio<1>> 类型 auto t = std::chrono::seconds(3); cout << t.count() << endl; // 输出值 3 // 构建一个200ms的时长类实例. duration<int64_t, ratio<1, 1000>> 类型. auto t2 = std::chrono::milliseconds(200); cout << t2.count() << endl; // 输出值 200 } std::timed_mutex tm_mtx; void timed_mutex_exam() { // 获取当前时间 auto now = std::chrono::steady_clock::now(); // 尝试在1s内持续获取锁. if (tm_mtx.try_lock_until(now + std::chrono::seconds(1))) { std::cout << "Successfully locked" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟长时间操作 tm_mtx.unlock(); // 释放锁 } else { std::cout << "Failed to lock" << std::endl; } } int main() { // 支持算术运算 auto t3 = std::chrono::seconds(30); auto t4 = t3 * 2; auto t5 = t3 + t4; cout << t4.count() << endl; cout << t5.count() << endl; // 用于其他函数中的"持续时间"参数 std::thread th(timed_mutex_exam); // 休眠500ms std::this_thread::sleep_for(std::chrono::milliseconds(500)); th.join(); return 0; } ``` <br><br><br> # timerfd 定时器 API > timerfd API 为 Linux 特有,自 Linux 2.6.25 起提供,位于头文件 `<sys/timerfd.h>` 中 timerfd 是 Linux 特有的 **利用文件描述符进行通知** 的定时器机制,可由 `select/poll/epoll` 进行监控[^5]。 相关系统调用如下,详细参数说明参见 [^5] - `timerfd_create(clockid, flags)`:**创建定时器**; - 第一参数 `clockid`,可选项为: - `CLOCK_MONOTONIC`:单调递增时钟(自**系统启动后**开始递增) - `CLOCL_REALTIME`:系统实时时钟 - `timerfd_settime(fd, flags, new_value, old_value)`:**设置定时器**(启动/停止)。 - 第二参数 `flags`,可选项为: - `0`:**相对时间**,**相较于 `timerfd_settime()` 调用时**。 - `TFD_TIMER_ABSTIME`:**绝对时间**(从系统时钟值 0 开始) - 第三、四参为 `struct itimerspec` 指针。 - `timerfd_gettime(fd, curr_value)`:获取间隔、**距离下次到期时间**。 - 第二参数为 `struct itimerspec` 指针 - 启动定时后,可**调用 `read(timerfd, buf, n)`** 读取 "**累计的超时次数**" ,要求 `buf` 必须是 **`uint64_t` 类型**。 - **通过 `close(timerfd)` 删除定时器**。 > [!caution] 每次调用 `timerfd_settime()` 重新设置,或者调用 `read()` 读取后,超时次数都会**归零重新累计**。 > [!info] 结构体 itimerspec > > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-BA4EE852CC3DD13B67944A5A5C9F4D38.png|620]] > > - `it_value`:指定超时时间(**首次到期时间**),根据 `timerfd_settime()` 的第二项参数 `flag` 值而不同: > - `it_interval`:指定**间隔时间** > - 若该字段非 0,则在 **`it_value` 首次到期后**,将根据该**间隔时间** 进行**周期性触发**。 > > [!NOTE] 关于 `timerfd_gettime()` 返回的到期时间 > > ![[_attachment/02-开发笔记/99-Unix 环境编程/时钟 API.assets/IMG-时钟 API-5492510E746EE510DC36A1DDEB2859DD.png|763]] ### 使用示例 ```cpp #include <cstdint> #include <sys/timerfd.h> #include <sys/epoll.h> #include <unistd.h> #include <cstdio> #include <cstdlib> #include <iostream> #include <string.h> using namespace std; int main() { // 创建定时器: // - CLOCK_MONOTONIC表示单调递增时钟 // - TFD_NONBLOCK: 在该fd上的读操作为非阻塞 // - TFD_CLOEXCE: exec之后, 该fd不会被新进程继承. int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); if (timerfd == -1) { perror("timerfd_create"); exit(EXIT_FAILURE); } // 创建epoll实例, 为timerfd注册监听 int epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } struct epoll_event ev, events[1]; ev.data.fd = timerfd; ev.events = EPOLLIN; epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &ev); // 设置启动定时器 // - 参数二 falg: 0 表示相对时间, 相对当前调用时刻 // - 参数三 new_value: 设置超时时间或间隔时间 // - 参数四 old_value: 用于获取定时器的前一设置; NULL表示不获取 // 参数3与4均为itimerspec结构 // struct itimerspec val; memset(&val, 0, sizeof(val)); val.it_value.tv_sec = 2; // 初始超时时间: 5s; val.it_interval.tv_sec = 7; // 间隔时间: 1s(在首次超时后, 以1s为间隔周期性触发) if (timerfd_settime(timerfd, 0, &val, nullptr) < 0) { perror("timerfd_settime"); close(timerfd); exit(EXIT_FAILURE); } while (true) { int nsfd = epoll_wait(epollfd, events, 10, -1); if (nsfd == -1) { perror("epoll_wait"); break; } // 读取已发生的到期次数 // 注: read读取的是自上次修改timerfd_settime()以来, 最后一次执行read()后所累积的到期次数. // 每次read读取后, 都会重新累计. if (events[0].data.fd == timerfd) { uint64_t expirations; // 要求使用uint64_t读取. read(timerfd, &expirations, sizeof(expirations)); cout << "Timer triggered: " << expirations << endl; } } close(timerfd); } ``` <br><br> # Buffer ## 闪念 > sudden idea ## 候选资料 > Read it later # ♾️参考资料 # Footnotes [^1]: 《C 语言程序设计:现代方法》 (P542) [^2]: 《UNIX 环境高级编程》 (P151) [^3]: 《C++ 并发编程实践(第 2 版)》(P95) [^4]: 《C++ 20 高级编程》(P647) [^5]: 《Linux-UNIX 系统编程手册》第 23.7 章,P420