RTU/mimo/plan/libtask定时器SIGEV_THREAD优化.md

152 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Plan: libtask 定时器优化 — timerfd 替代 SIGEV_THREAD
**日期**: 2026-06-15
**目标**: 消除定时器每次超时创建/销毁内核线程的开销(~1000线程/秒)
---
## 问题根因
`myTask.c` 使用 `timer_create(CLOCK_REALTIME, SIGEV_THREAD, ...)` 模式每次超时内核创建新线程执行回调。9 个 app × 3 个定时器(10/100/1000ms) = 27 个定时器,每秒约 1000 次线程创建/销毁。回调仅做 `task_event_send`(几微秒),线程创建开销远大于实际工作。
## 方案概要
用 Linux **timerfd** + **epoll** + **单例管理器线程** 替代 `SIGEV_THREAD`
- 每个定时器用 `timerfd_create` 创建文件描述符
- 一个持久化 manager 线程用 `epoll_wait` 监听所有 timerfd
- timerfd 可读时在 manager 线程中调用原始回调
- API 签名全部不变,调用方零修改
## 涉及文件
| 文件 | 改动 |
|------|------|
| `src/public/libtask/src/myTask.c` | **主要修改** — 重写定时器子系统 |
| `release/inc/myTask.h` | **无改动**`stru_task_timer_t` 是不透明 `void*` |
## 结构体变更
```c
// myTask.c 内部 (对外不可见)
typedef struct
{
char name[64];
int timerfd; // 替代 timer_t timerid
timer_func_cb fun;
void *arg;
uint32_t timeout_ms;
int flags;
int active;
int running; // 1=回调执行中
int in_epoll; // 1=timerfd 已注册到 epoll
pthread_mutex_t mutex;
pthread_cond_t cond; // 新增: stop/destroy 等回调完成
int ref_count;
} stru_task_timer;
```
变更: `timer_t timerid``int timerfd`,新增 `pthread_cond_t cond`、`int in_epoll`
## 管理器线程设计
```
全局状态:
static int g_epfd; // epoll 实例
static int g_eventfd; // wakeup eventfd
static pthread_t g_mgr_thread;
static pthread_mutex_t g_mgr_mutex; // 串行化调用者 epoll_ctl
static int g_mgr_init_done;
timer_manager_thread():
g_epfd = epoll_create1(0)
g_eventfd = eventfd(0, EFD_NONBLOCK)
epoll_ctl(ADD, g_eventfd) // wakeup fd 永久在 epoll 中
while(1):
epoll_wait(events, -1)
for each ready fd:
if fd == g_eventfd: read() 清除; continue
read(fd, &expirations) // 清除过期计数
lock(p_timer->mutex)
if active:
running = 1; unlock
fun(arg) // 调用原始回调
lock
running = 0
cond_broadcast(&cond)
if !PERIODIC: active = 0
unlock
```
## 关键同步设计
### 1. 动态 epoll 增删
- **添加** (`task_timer_start`): 调用者持有 `g_mgr_mutex``epoll_ctl(ADD)`,然后 write eventfd 唤醒 manager
- **删除** (`task_timer_stop`): 调用者持有 `g_mgr_mutex``epoll_ctl(DEL)`
- Manager 线程**永不**做 `epoll_ctl`,避免跨线程 epoll 操作竞态
- `g_mgr_mutex` 仅防护调用者之间并发的 epoll_ctl
### 2. stop/destroy 同步(替代 usleep 自旋)
`running` + `condvar` 替代当前 `while(running) usleep(1000)` 忙等:
```
stop 路径:
lock(mutex)
active = 0
timerfd_settime(its={0,0}) // disarm
while(running):
cond_wait(&cond, &mutex) // 阻塞等待,零 CPU
if in_epoll: epoll_ctl(DEL)
unlock
manager 路径 (回调结束后):
lock(mutex)
running = 0
cond_broadcast(&cond) // 唤醒 stop 等待者
if !PERIODIC: active = 0
unlock
```
**锁顺序注意**: stop 中 `epoll_ctl(DEL)` 之前先 `unlock(&p->mutex)``lock(&g_mgr_mutex)`,避免与 start 路径的 `lock(&p->mutex)``lock(&g_mgr_mutex)` 形成 ABBA 死锁。
### 3. 惰性初始化
首次 `task_timer_start` 时调用 `timer_manager_ensure_init()`,用 `g_mgr_init_done` + `g_mgr_mutex` 做双重检查锁定,确保 manager 线程仅创建一次。
## API 实现概要
### task_timer_create
1. 参数校验 → calloc 结构体 → 初始化字段
2. `timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK)`
3. 初始化 mutex + cond → 返回指针(未启动)
### task_timer_start
1. `timer_manager_ensure_init()` 惰性初始化 manager
2. lock → `timerfd_settime` 设间隔 → 若 `!in_epoll``epoll_ctl(ADD)` + write eventfd → `active=1` → unlock
### task_timer_stop
1. lock → `timerfd_settime({0,0})` disarms → `active=0`
2. `while(running) cond_wait` 等回调完成
3.`in_epoll`: unlock → lock g_mgr → `epoll_ctl(DEL)` → unlock g_mgr → lock → `in_epoll=0` → unlock
### task_timer_destroy
1. 调用 `task_timer_stop`(含同步等待 + epoll 移除)
2. lock → `ref_count--`;若 >0 直接返回
3. `close(timerfd)` → 销毁 mutex/cond → `free`
### task_timer_restart
`stop()` → 更新 `timeout_ms``start()`
### task_timer_is_active
lock → 读 `active` → unlock → 返回(不变)
## 验证
1. `./release/build.sh` 编译零错误零警告
2. 确认 `myTask.h` 未改动API 透明兼容)
3. 确认 app_sys.cpp 等所有调用方无需修改9 个 app 线程的定时器用法不变)
4. `./test/RTU` 启动运行,验证各 app 的 run_cnt 正常递增