%%
# 纲要
> 主干纲要、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