为何使用SIGEV_THREAD时timer_create会提前创建线程?
为什么使用SIGEV_THREAD时timer_create阶段就创建了线程?
这是个很细节的问题,其实背后是POSIX标准允许的实现差异加上glibc(Linux常用C标准库)的具体优化策略导致的,我来一步步给你拆解清楚:
1. POSIX标准的灵活性
POSIX对于SIGEV_THREAD模式的定义是:定时器到期时需执行指定的sigev_notify_function,但并没有强制要求必须在到期时才创建线程。标准允许两种实现方式:
- 方式一:每次定时器到期时动态创建新线程来执行回调
- 方式二:提前创建一个常驻线程,专门负责处理所有
SIGEV_THREAD类型的定时器事件,到期时直接在这个线程里(或通过线程池)执行回调
Linux上的glibc选择了第二种方式,这就是你看到timer_create时就出现新线程的核心原因。
2. glibc的具体实现逻辑
当你调用timer_create并设置sigev_notify = SIGEV_THREAD时,glibc会立即创建一个名为__timer_thread的内部线程(也就是你线程列表里SPID为9946的那个)。这个线程的作用是:
- 监听所有使用
SIGEV_THREAD的定时器到期事件 - 当定时器到期时,调用你传入的
sigev_notify_function并传递sigev_value参数 - 对于你代码里的一次性定时器(
it_interval.tv_sec=0),这个线程在处理完一次回调后可能会退出,但创建动作确实是在timer_create阶段完成的
这种设计是为了性能优化——频繁创建销毁线程的开销很大,提前复用一个线程(或线程池)能显著提升定时器密集场景下的运行效率。
3. 和SIGEV_SIGNAL的对比
当使用SIGEV_SIGNAL时,定时器到期的通知是通过信号发送给进程的:
- 进程可以通过注册信号处理函数,或者用
sigwait等方式等待信号 - 整个过程不需要额外线程,信号处理是在进程的现有线程(或内核调度的线程)中执行的,所以
timer_create阶段不会创建新线程
你的代码与线程列表验证
整理后的示例代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <time.h> #include <assert.h> #include <string.h> void timer_thread(union sigval sv) { printf("Timer callback triggered, value: %d\n", sv.sival_int); } int main(){ printf ("My process ID %d\n", getpid()); int status = 0; timer_t timer_id; memset (&timer_id, 0, sizeof (timer_t)); int j = 10; struct itimerspec ts; struct sigevent se; se.sigev_notify = SIGEV_THREAD; se.sigev_value.sival_int = j; se.sigev_notify_function = timer_thread; ts.it_value.tv_sec = 3; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec =0; ts.it_interval.tv_nsec =0; status = timer_create (CLOCK_REALTIME, &se, &timer_id); printf ("timer_id is %ld\n",(long int)timer_id); assert (!status && "Create timer"); status = timer_settime (timer_id, 0, &ts, 0); assert (!status && "Set timer"); // 注意:原代码直接return会导致进程提前退出,需等待定时器触发 sleep(5); return 0; }
你观察到的线程列表:
PID SPID TTY TIME CMD 9945 9945 pts/20 00:00:18 timer_create 9945 9946 pts/20 00:00:00 timer_create
其中SPID=9946的就是glibc提前创建的定时器处理线程。
额外验证点
如果你把代码改成周期性定时器(比如ts.it_interval.tv_sec=2),运行进程后再查看线程列表,会发现始终只有这两个线程,不会每次到期都新增——这就验证了glibc是复用线程而非动态创建的逻辑。
内容的提问来源于stack exchange,提问作者Mathi.J
相关产品推荐
相关产品推荐

