由一个C定时执行任务的程序引发的思考
程序
这里使用C写了个定时执行的程序,见a.c
//a.c
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void* send_signal_every_second(void* args) {
while(1) {
kill(getpid(), SIGALRM);
sleep(1);
}
}
void sighandler(int);
void *print_hello_when_receive_signal(void* args) {
signal(SIGALRM, sighandler);
while(1) {} // exec sighandler every one seconds, signal will interrupt this while in worker thread.
}
void sighandler(int signum) {
printf("Get signal %ld\n", time(NULL));
}
int main() {
pthread_t timer;
pthread_t worker;
int res2 = pthread_create(&worker, NULL, print_hello_when_receive_signal, NULL);
if(res2) {
printf("Error in create thread print_hello_when_receive_signal \n");
exit(0);
}
int res = pthread_create(&timer, NULL, send_signal_every_second, NULL);
if(res) {
printf("Error in create thread send_signal_every_second \n");
exit(0);
}
pthread_join(worker, NULL);
pthread_join(timer, NULL);
}
Makefile
a.out: a.c
gcc a.c -lpthread
除了主线程之外,设计了两个线程。其中一个叫timer,另外一个叫worker。
timer线程工作入口函数为send_signal_every_second
,负责定时发送信号,程序中通过sleep函数,每隔1s发送SIGALRM信号给当前进程(getpid()
)。
worker线程工作的入口函数为print_hello_when_receive_signal
,进入之后,首先为进程的SIGALRM信号设置了一个sighandler处理函数,然后进入一个循环。
执行的时候,就能看到终端上,每隔1s打印一行信息。
对代码进行复盘
- 进程收到signal,是另外开辟一个线程来执行吗sighandler吗?还是在当前线程?
当前代码的进程会产生三个线程:主线程,worker和timer线程。主线程在main函数的最后等另外两个线程。对于第一个问题,sighandler不会开新线程来跑,而是在现有的线程中随机运行,甚至可能在主线程上执行。参考https://zhuanlan.zhihu.com/p/460255685。worker的死循环可以不用,然worker线程很快得自然结束,sighander在主线程或timer线程上同样会被每隔1s调用一次。
如何在linux下看某进程的线程?ls -l /proc/pid/task
。可以看到每个线程一个编号。
-
pthread_join之后,主线程是什么状态?
首先可以明确pthread_join
函数是非阻塞的。执行完,主线程在等到待join线程结束之前不会结束。主线程中是什么状态待定(TODO). -
某线程通过sleep函数睡眠的时候,能被signal唤醒吗?
通过nanosleep函数(对应到了系统调用)的manual说明,是可以唤醒的。这个从常识中也可以得到这个结论:当某个进程sleep或等待输入的时候,也是可以通过ctrl+c将其终止的。
nanosleep() suspends the execution of the calling thread until either at least the time specified in *req has elapsed, or the delivery of a signal that triggers the invocation of a handler in the calling thread or that terminates the process.
If the call is interrupted by a signal handler, nanosleep() returns -1, sets errno to EINTR, and writes the remaining time into structure pointed by rem unless rem is NULL. The value of *rem can then be used to call nanosleep() again and complete the specified pause (but see notes).
从字面意思上看,nanosleep的时间控制可以到$10^{-9}$。若某个进程sigHandler打断了nanosleep,并很busy,一直在一个for循环里,且没有使用类似sleep/read/write的系统调用(单纯的ALU计算),则for中切入signal中断的机会在tick进程切换过程中,所以时间可能会比真实发送信号的时间多大概一个tick的时间。
从https://superuser.com/questions/101183/what-is-a-cpu-tick, https://learn.microsoft.com/en-us/dotnet/api/system.datetime.ticks?redirectedfrom=MSDN&view=net-7.0#System_DateTime_Ticks可以看到,windows和linux两个tick直接相差的时间设置的都是100ns, 即10000个tick每毫秒。每个tick会引起操作系统切换一次进程。可以看到测试。
- 若nanosleep被打断了,执行静默的thread是不是就立即静默,并进入到下一条指令的执行?
通过测试,观察到,被外部signal打断即退出sleep状态了。