c定时执行任务

由一个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打印一行信息。

对代码进行复盘

  1. 进程收到signal,是另外开辟一个线程来执行吗sighandler吗?还是在当前线程?

当前代码的进程会产生三个线程:主线程,worker和timer线程。主线程在main函数的最后等另外两个线程。对于第一个问题,sighandler不会开新线程来跑,而是在现有的线程中随机运行,甚至可能在主线程上执行。参考https://zhuanlan.zhihu.com/p/460255685。worker的死循环可以不用,然worker线程很快得自然结束,sighander在主线程或timer线程上同样会被每隔1s调用一次。

如何在linux下看某进程的线程?ls -l /proc/pid/task。可以看到每个线程一个编号。

  1. pthread_join之后,主线程是什么状态?
    首先可以明确pthread_join函数是非阻塞的。执行完,主线程在等到待join线程结束之前不会结束。主线程中是什么状态待定(TODO).

  2. 某线程通过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会引起操作系统切换一次进程。可以看到测试。

  1. 若nanosleep被打断了,执行静默的thread是不是就立即静默,并进入到下一条指令的执行?
    通过测试,观察到,被外部signal打断即退出sleep状态了。
posted @ 2023-05-03 22:42  zwlwf  阅读(144)  评论(0)    收藏  举报