在开发多线程应用时,开发人员一般都会考虑线程安全,会使用 pthread_mutex
去保护全局变量。如果应用中使用了信号,而且信号的产生不是因为程序运行出错,而是程序逻辑需要,譬如 SIGUSR1、SIGRTMIN 等,信号在被处理后应用程序还将正常运行。在编写这类信号处理函数时,应用层面的开发人员却往往忽略了信号处理函数执行的上下文背景,没有考虑编写安全的信号处理函数的一些规则。本文首先介绍编写信号处理函数时需要考虑的一些规则;然后举例说明在多线程应用中如何构建模型让因为程序逻辑需要而产生的异步信号在指定的线程中以同步的方式处理。
Linux 多线程应用中,每个线程可以通过调用 pthread_sigmask()
设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如 SIGSEGV;另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。
当一个线程调用 pthread_create()
创建新的线程时,此线程的信号掩码会被新创建的线程继承。
POSIX.1 标准定义了一系列线程函数的接口,即 POSIX threads(Pthreads)。Linux C 库提供了两种关于线程的实现:LinuxThreads 和 NPTL(Native POSIX Threads Library)。LinuxThreads 已经过时,一些函数的实现不遵循POSIX.1 规范。NPTL 依赖 Linux 2.6 内核,更加遵循 POSIX..1 规范,但也不是完全遵循。
基于 NPTL 的线程库,多线程应用中的每个线程有自己独特的线程 ID,并共享同一个进程ID。应用程序可以通过调用 kill(getpid(),signo)
将信号发送到进程,如果进程中当前正在执行的线程没有阻碍此信号,则会被中断,线号处理函数会在此线程的上下文背景中执行。应用程序也可以通过调用 pthread_kill(pthread_t thread, int sig)
将信号发送给指定的线程,则线号处理函数会在此指定线程的上下文背景中执行。
基于 LinuxThreads 的线程库,多线程应用中的每个线程拥有自己独特的进程 ID,getpid()
在不同的线程中调用会返回不同的值,所以无法通过调用 kill(getpid(),signo)
将信号发送到整个进程。
下文介绍的在指定的线程中以同步的方式处理异步信号是基于使用了 NPTL 的 Linux C 库。请参考“Linux 线程模型的比较:LinuxThreads 和 NPTL”和“pthreads(7) - Linux man page”进一步了解 Linux 的线程模型,以及不同版本的 Linux C 库对 NPTL 的支持。
信号的产生可以是:
- 用户从控制终端终止程序运行,如 Ctrk + C 产生 SIGINT;
- 程序运行出错时由硬件产生信号,如访问非法地址产生 SIGSEGV;
- 程序运行逻辑需要,如调用
kill
、raise
产生信号。
因为信号是异步事件,即信号处理函数执行的上下文背景是不确定的,譬如一个线程在调用某个库函数时可能会被信号中断,库函数提前出错返回,转而去执行信号处理函数。对于上述第三种信号的产生,信号在产生、处理后,应用程序不会终止,还是会继续正常运行,在编写此类信号处理函数时尤其需要小心,以免破坏应用程序的正常运行。关于编写安全的信号处理函数主要有以下一些规则:
- 信号处理函数尽量只执行简单的操作&