Linux 信号概念
程序在执行的时候,几乎任何时刻都会反生事件。 信号通常用来向一个进程通知事件。 信号是不可提前预知的,所以信号是异步的。 信号随时都可能发生,接收信号的进程也可以没有控制权。 每个信号名都以SIG开头,信号名的定义在<signal.h>中。 信号名一般都是宏,内部通常是一个正整数。
许多情况下都会出现信号,如硬件异常,非法的内存引用,软件异常等等。 kill函数就是向目标进程发一个SIGKILL信号,就和kill命令一样。 当进程收到一个信号,他可以对信号采取如下三种措施之一 --忽略这个信号; --捕获这个信号,这需要执行一段称为信号处理器的特殊代码; --允许执行信号的默认操作。
当信号发生的事件出现时,比如硬件异常,就会产生一个针对具体进程的信号。
当进程对发送给它的信号采取措施的时候,就叫该信号被传送。
产生信号和传递信号之间的时间间隔称为信号未决。
信号的部署是指进程如何响应信号。
进程可以忽略信号、用默认操作处理信号、响应这个信号。
进程不能简单的通过判断一个变量,例如errno来判断是否出现了一个信号。
关于信号中断逻辑的理解
如果抛开线程本身通过精心设计的 checkpoint 被『杀死』逻辑(例如每次循环检查 flag ,
主线程只需要设置一下 flag 就可以达到『杀死』子线程),那么事实上没有手段可以『杀死』一个正在『用户态』的线程,
至少在linux平台,是没有办法强制停止一个正在进行cpu密集型计算作业线程的,除了直接 kill 进程,别无他法。
不信你可以写写代码试试,比如用pthread_create一个计算斐波那契数列第100位的程序,
假设计算过程在你的机器上持续10秒,然后主线程sleep 1秒之后去尝试杀掉它,
哪怕你用pthread_kill,pthread_cancel这些函数依旧是无法杀掉它的,它依然会跑10秒。
linux就没有提供『真正意义上的强制杀死线程的api』(windows倒是提供了这么一个危险的api,但也不鼓励使用),
而这些所谓的『杀死』线程的api,都是一种信号量响应,它需要条件才能生效——你的线程在『内核态』。
只有你的线程在内核状态,才能响应同样是内核范畴的signal。
『内核态』可以是sleep状态,sync状态,io-blocking状态等等,
所以假设你在你刚刚的计算斐波那契数列的函数中,每次递归或者循环的时候都sleep(0),userland交还给操作系统,
你的pthread_cancel将会生效。
很多高级语言,脚本语言通过对sleep(0)封装来实现的userland context的交还,
从而实现信号检测,像is_interrupt()一类,简单来说就是人为制造内核态的checkpoint。
所以『杀掉』一个线程的精髓在于『适时地让你的程序陷入内核态』(最常见的手段就是sleep)并处理可能的故障以及资源的回收。
现在就需要搞清楚,线程的『卡住』是卡在什么地方?有可能是内核态,比如阻塞在socket的read上,
那么就是io-blocking状态,此时你用pthread_cancel或者pthread_kill都是可以『杀死』线程的。
如果线程卡在一个算法上面,这个算法本身过于复杂,要跑很久,你想在跑完之前就『杀死』它,那就没办法直接杀掉这个线程。
经过测试,以下程序无法正常结束。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <stdio.h> #include <pthread.h> #include <signal.h> #include <unistd.h> void* thread_func(void* arg) { int n = 1000000; long long fib1 = 0, fib2 = 1, fib; int i = 0; while (1) { for (i = 2; i <= n; i++) { fib = fib1 + fib2; fib1 = fib2; fib2 = fib; } } return 0; } int main() { pthread_t thread1; int num1 = 1; int ret; pthread_create(&thread1, NULL, thread_func, (void*)&num1); sleep(3); printf("begin pthread_kill\n"); signal(SIGUSR2, SIG_IGN); ret = pthread_kill(thread1, SIGUSR2); if (ret != 0) { printf("发送信号失败\n"); return 1; } printf("end pthread_kill\n"); pthread_join(thread1, NULL); return 0; }
在Linux中,主线程执行CPU密集型计算时,可能会占用大量的CPU资源,导致其他线程无法获得足够的CPU时间片,从而影响系统的响应性能。
为了避免这种情况,Linux内核会定期发送信号给进程,例如SIGALRM信号,以便让进程中的线程有机会释放CPU资源,从而保证系统的响应性能。
当主线程执行CPU密集型计算时,如果收到了信号,它会暂停当前的计算,执行信号处理函数,然后继续执行之前的计算。
这种方式可以让主线程在保证计算进度的同时,响应信号,从而保证系统的响应性能。因此,即使主线程执行CPU密集型计算,也可以被信号打断。