一、为什么要有线程的信号处理
由于多线程程序中线程的执行状态是并发的,因此当一个进程收到一个信号时,那么究竟由进程中的哪条线程响应这个信号就是不确定的,只能取决于哪条线程刚好在信号达到的瞬间被调度,这种不确定性在程序逻辑中一般是不能接受的。
二、解决办法
1、在多线程进程中选定某条线程去响应信号
2、其余线程对该信号进行屏蔽
三、相关函数API接口
1、发送信号给指定线程
// 在进程内部,只允许在线程之间进行发送 int pthread_kill(pthread_t thread, int sig); // 接口说明 返回值:成功返回0,失败返回错误码 参数thread:接收信号的线程号 参数sig:待发送的信号 // 在进程之间进行的信号发送 int kill(pid_t pid, int sig); // 接口说明 返回值:成功返回0,失败返回-1 参数pid:接受信号的进程号 参数sig:待发送的信号
2、发送带参数的信号给指定线程
// 发送带参数的信号给指定线程 // 线程间 int pthread_sigqueue(pthread_t thread, int sig, const union sigval value); // 接口说明 返回值:成功返回0,失败返回-1 参数thread:待接收信号的线程号 参数sig:待发送的信号 参数value:额外携带的参数 // 进程间 int sigqueue(pid_t pid, int sig, const union sigval value); // 接口说明 返回值:成功返回0,失败返回-1 参数pid:待接收信号的进程号 参数sig:待发送的信号 参数value:额外携带的参数
3、屏蔽指定信号
// 屏蔽指定信号 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 接口说明 返回值:成功返回0,失败返回-1 // 参数解析: 1、how:操作命令字,比如阻塞、解除阻塞等 SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞) SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号自动解除) SIG_UNBLOCK:解除set中的信号 2、set:当前要操作的信号集 3、oldset:若为非空,则将原有阻塞信号集保留到该oldset中 注意:该函数的操作参数不是单个信号,而是信号集。 // 信号集操作函数组 int sigemptypset(sigset_t *set); // 清空信号集set int sigfillset(sigset_t *set); // 将所有信号加入信号集set中 int sigaddset(sigset_t *set, int signum); // 将信号signum添加到信号集set中 int sigdelset(sigset_t *set, int signum); // 将信号signum从信号集set中剔除 int sigsimember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中
四、案例
1、使用线程结合信号的方式完成数据的接收和发送,要求一条线程发送数据同时发送信号指定某条线程接收数据,另外有多余线程做伪任务。
// 多线程信号处理的案例 #include <stdio.h> #include <pthread.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <signal.h> char data[100]; pthread_t tid1, tid2, tid3; // 信号响应函数 void recv_handler(int sig) { printf("\nmy tid is %ld\n", pthread_self()); printf("read data: %s\b", data); memset(data, 0, sizeof(data)); } // 线程1的例程函数 void *routine1(void *arg) { printf("I am recv_routine, my tid = %ld\n", tid1); // 设置线程分离 pthread_detach(pthread_self()); while(1) { printf("please input data:\n"); fgets(data, sizeof(data), stdin); pthread_kill(tid2, 34); // 给线程2发送信号 printf("send data success\n"); } } // 线程2的例程函数,用来接收数据 void *routine2(void *arg) { // 注册信号响应函数 signal(34, recv_handler); printf("I am routine2, my tid = %ld\n", tid2); // 设置线程分离 pthread_detach(pthread_self()); while(1) { pause(); } } // 线程3的例程函数 void *routine3(void *arg) { printf("I am routine3, my tid = %ld\n", tid3); // 设置线程分离 pthread_detach(pthread_self()); while(1) { pause(); } } int main(int argc, char *argv[]) { // 创建线程1,用来发送和接收数据 errno = pthread_create(&tid1, NULL, routine1, NULL); if(errno == 0) { printf("pthread create routine1 success, tid = %ld\n", tid1); } else { perror("pthread create routine1 fail\n"); } // 创建线程2,用来做多余线程 errno = pthread_create(&tid2, NULL, routine2, NULL); if(errno == 0) { printf("pthread create routine2 success, tid = %ld\n", tid2); } else { perror("pthread create routine2 fail\n"); } // 创建线程3,用来做多余线程 errno = pthread_create(&tid3, NULL, routine3, NULL); if(errno == 0) { printf("pthread create routine3 success, tid = %ld\n", tid3); } else { perror("pthread create routine3 fail\n"); } // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出 // 或者加上while(1)等让主函数不退出 pthread_exit(0); return 0; }
2、使用线程结合信号的方式完成数据的接收和发送,要求一条线程完成数据的发送和接收,另外两个线程屏蔽信号,做伪任务。
// 多线程信号处理的案例 #include <stdio.h> #include <pthread.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <signal.h> char data[100]; sigset_t sigs_set; // 信号集 pid_t pid; pthread_t tid1, tid2, tid3; // 信号响应函数 void recv_handler(int sig) { printf("\nmy tid is %ld\n", pthread_self()); printf("read data: %s\b", data); memset(data, 0, sizeof(data)); } // 线程1的例程函数 void *routine1(void *arg) { printf("I am routine1, my tid = %ld\n", tid1); // 设置线程分离 pthread_detach(pthread_self()); while(1) { printf("please input data:\n"); fgets(data, sizeof(data), stdin); printf("send data success\n"); kill(pid, 34); // 给进程(所有线程)发送信号 } } // 线程2的例程函数,用来接收数据 void *routine2(void *arg) { printf("I am routine2, my tid = %ld\n", tid2); // 屏蔽(阻塞)信号集中的信号 sigprocmask(SIG_BLOCK, &sigs_set, NULL); // 设置线程分离 pthread_detach(pthread_self()); while(1) { pause(); } } // 线程3的例程函数 void *routine3(void *arg) { printf("I am routine3, my tid = %ld\n", tid3); // 设置线程分离 pthread_detach(pthread_self()); // 屏蔽(阻塞)信号集中的信号 sigprocmask(SIG_BLOCK, &sigs_set, NULL); while(1) { pause(); } } int main(int argc, char *argv[]) { // 注册信号响应函数 signal(34, recv_handler); sigemptyset(&sigs_set); // 清空信号集 sigaddset(&sigs_set, 34); // 把34信号加到信号集中 pid = getpid(); // 获取进程号 // 创建线程1,用来发送和接收数据 errno = pthread_create(&tid1, NULL, routine1, NULL); if(errno == 0) { printf("pthread create routine1 success, tid = %ld\n", tid1); } else { perror("pthread create routine1 fail\n"); } // 创建线程2,用来做多余线程 errno = pthread_create(&tid2, NULL, routine2, NULL); if(errno == 0) { printf("pthread create routine2 success, tid = %ld\n", tid2); } else { perror("pthread create routine2 fail\n"); } // 创建线程3,用来做多余线程 errno = pthread_create(&tid3, NULL, routine3, NULL); if(errno == 0) { printf("pthread create routine3 success, tid = %ld\n", tid3); } else { perror("pthread create routine3 fail\n"); } // 一定要加这个,否则主函数直接退出,相当于进程退出,所有线程也退出 // 或者加上while(1)等让主函数不退出 pthread_exit(0); return 0; }
五、总结
多线程进程中的信号处理可以采用选定某一条线程来接收信号,其余线程屏蔽该信号的做法,可以结合案例加深对多线程中信号的处理。