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

5.0 KiB
Raw Permalink Blame History

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*

结构体变更

// 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 timeridint timerfd,新增 pthread_cond_t condint 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_mutexepoll_ctl(ADD),然后 write eventfd 唤醒 manager
  • 删除 (task_timer_stop): 调用者持有 g_mgr_mutexepoll_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_epollepoll_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_msstart()

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 正常递增