信号捕获和线程终止的问题 - C
Trouble with signal catching and thread termination - C
我正在用 c 编写一个程序,它使用线程,我还想从用户那里捕获 Ctrl+C 信号。所以,在我进行多线程处理之前,我会捕获信号。
我的主线程(我的意思是除了程序运行的实际主线程之外)是一种处理用户输入的方法,我也将这个线程加入到主程序线程中。
问题是,当测试并按下 Ctrl+C 退出程序时,
负责接收用户输入的线程在我按下键盘上的 "return" 之前不会关闭 - 就像它卡在无限循环中一样。
通过键入 'q' 退出时,所有线程都正常结束。
我使用一个全局变量exit_flag来指示线程完成它们的循环。
还有,在init_radio_stations方法中有另一个单线程创建,它以完全相同的方式循环——在exit_flag 状态,并且此线程正确关闭
这是我的主循环代码:
void main_loop()
{
status_type_t rs = SUCCESS;
pthread_t thr_id;
/* Catch Ctrl+C signals */
if(SIG_ERR == signal(SIGINT, close_server)) {
error("signal() failed! errno = ");
}
printf("\n~ Welcome to radio_server! ~\n Setting up %d radio stations... ", srv_params.num_of_stations);
init_radio_stations();
printf("Done!\n\n* Hit 'q' to exit the application\n* Hit 'p' to print stations & connected clients info\n");
/* Create and join a thread to handle user input */
if(pthread_create(&thr_id, NULL, &rcv_usr_input, NULL)) {
error("main_loop pthread_create() failed! errno = ");
}
if(pthread_join(thr_id, NULL)) {
error("main_loop pthread_join() failed! errno = ");
}
}
close_server方法:
void close_server(int arg)
{
switch(arg) {
case SIGINT: /* 2 */
printf("\n^C Detected!\n");
break;
case ERR: /* -1 */
printf("\nError occured!\n");
break;
case DEF_TO: /* 0 */
printf("\nOperation timed-out!\n");
break;
default: /* will handle USER_EXIT, and all other scenarios */
printf("\nUser abort!\n");
}
printf("Signaling all threads to free up all resources and exit...\n");
/* Update exit_flag, and wait 1 sec just in case, to give all threads time to close */
exit_flag = TRUE;
sleep(1);
}
和rcv_usr_input句柄代码:
void * rcv_usr_input(void * arg_p)
{
char in_buf[BUFF_SIZE] = {0};
while(FALSE == exit_flag) {
memset(in_buf, 0, BUFF_SIZE);
if(NULL == fgets(in_buf, BUFF_SIZE, stdin)) {
error("fgets() failed! errno = ");
}
/* No input from the user was received */
if('[=12=]' == in_buf[0]) {
continue;
}
in_buf[0] = tolower(in_buf[0]);
if( ('q' == in_buf[0]) && ('\n' == in_buf[1]) ) {
close_server(USER_EXIT);
} else {
printf("Invalid input!\nType 'q' or 'Q' to exit only\n");
}
}
printf("User Input handler is done\n");
return NULL;
}
我猜我的问题与在主循环结束时加入使用 rcv_usr_input 的线程有关,但我不知道是什么正是导致这种行为的原因。
我很乐意得到一些帮助,谢谢
解释很直接。
fgets(in_buf, BUFF_SIZE, stdin);
该调用会阻塞线程,直到它收到一行输入。也就是说,它不会 return 直到输入换行符或输入 BUFF_SIZE-1
个字符。
因此,即使信号处理程序将 exit_flag
设置为 FALSE
,rcv_usr_input
线程也不会看到它,直到它从 fgets
解除阻塞。当您按下 "return".
时会发生这种情况
根据 http://www.cplusplus.com/reference/cstdio/fgets/,fgets 会阻塞,直到读取了指定数量的字节。
我建议尝试 fread 或其他一些不阻塞的输入接收函数,然后一次只读取一个字节。下面是可以帮助您的示例代码:
if (fread(in_buf, 1,1, stdin) > 0){
//character has been read
}
而且我不会担心你的信号处理程序中的额外睡眠语句,因为它最多会导致强制退出延迟。
Mike 和 Kaylum 正确地识别了 fgets()
阻塞的根本问题。然而,更大的问题仍然存在:当进程收到 SIGINT
时如何终止阻塞线程。有几种解决方案。
特遣队:
一种解决方案是分离阻塞线程,因为分离线程不会阻止进程在最后一个非分离线程终止时终止。通过在其上调用 pthread_detach()
来分离线程,例如,
#include <pthread.h>
// Called by pthread_create()
static void* start(void* arg)
{
pthread_detach();
...
}
或者通过创建具有 PTHREAD_CREATE_DETACHED
属性的线程,例如,
#include <pthread.h>
...
pthread_attr_t attr;
(void)pthread_attr_init(&attr);
(void)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
...
(void)pthread_t thread;
(void)pthread_create(&thread, &attr, ...);
请注意 pthread_join()
应该 而不是 在分离的线程上调用。
信号转发:另一种解决方案是不分离阻塞线程,而是通过pthread_kill()
将SIGINT
之类的信号转发到线程,如果信号有未在阻塞线程上收到,例如,
#include <pthread.h>
#include <signal.h>
...
static pthread_t thread;
...
static void handle_sigint(int sig)
{
if (!pthread_equal(thread, pthread_self()) // Necessary
(void)pthread_kill(thread, SIGINT);
}
...
sigaction_t sigaction;
sigaction.sa_mask = 0;
sigaction.sa_flags = 0;
sigaction.sa_handler = handle_sigint;
(void)sigaction(SIGHUP, &sigaction, ...);
...
(void)pthread_create(&thread, ...);
...
(void)pthread_join(thread, ...);
...
这将导致阻塞函数 return,errno
设置为 EINTR
。
请注意,未指定在多线程进程中使用 signal()
。
线程取消:另一种解决方案是通过pthread_cancel()
取消阻塞线程,例如
#include <pthread.h>
...
static void cleanup(...)
{
// Release allocated resources
...
}
...
static void* start(void* arg)
{
pthread_cleanup_push(cleanup, ...);
for (;;) {
...
// Call the blocking function
...
}
pthread_cleanup_pop(...);
...
}
....
static void handle_sigint(int sig)
{
(void)pthread_cancel(thread);
}
...
sigaction_t sigaction;
sigaction.sa_mask = 0;
sigaction.sa_flags = 0;
sigaction.sa_handler = handle_sigint;
(void)sigaction(SIGHUP, &sigaction, ...);
...
(void)pthread_create(&thread, ..., start, ...);
...
(void)pthread_join(thread, ...);
...
对于在对 select()
或 poll()
的调用中阻塞的线程,还有另一种解决方案:创建一个文件描述符,阻塞函数也在该文件描述符上等待,并在收到适当的请求后关闭该描述符信号——但可以说,该解决方案超出了这个问题的范围。
我正在用 c 编写一个程序,它使用线程,我还想从用户那里捕获 Ctrl+C 信号。所以,在我进行多线程处理之前,我会捕获信号。
我的主线程(我的意思是除了程序运行的实际主线程之外)是一种处理用户输入的方法,我也将这个线程加入到主程序线程中。
问题是,当测试并按下 Ctrl+C 退出程序时, 负责接收用户输入的线程在我按下键盘上的 "return" 之前不会关闭 - 就像它卡在无限循环中一样。
通过键入 'q' 退出时,所有线程都正常结束。
我使用一个全局变量exit_flag来指示线程完成它们的循环。
还有,在init_radio_stations方法中有另一个单线程创建,它以完全相同的方式循环——在exit_flag 状态,并且此线程正确关闭
这是我的主循环代码:
void main_loop()
{
status_type_t rs = SUCCESS;
pthread_t thr_id;
/* Catch Ctrl+C signals */
if(SIG_ERR == signal(SIGINT, close_server)) {
error("signal() failed! errno = ");
}
printf("\n~ Welcome to radio_server! ~\n Setting up %d radio stations... ", srv_params.num_of_stations);
init_radio_stations();
printf("Done!\n\n* Hit 'q' to exit the application\n* Hit 'p' to print stations & connected clients info\n");
/* Create and join a thread to handle user input */
if(pthread_create(&thr_id, NULL, &rcv_usr_input, NULL)) {
error("main_loop pthread_create() failed! errno = ");
}
if(pthread_join(thr_id, NULL)) {
error("main_loop pthread_join() failed! errno = ");
}
}
close_server方法:
void close_server(int arg)
{
switch(arg) {
case SIGINT: /* 2 */
printf("\n^C Detected!\n");
break;
case ERR: /* -1 */
printf("\nError occured!\n");
break;
case DEF_TO: /* 0 */
printf("\nOperation timed-out!\n");
break;
default: /* will handle USER_EXIT, and all other scenarios */
printf("\nUser abort!\n");
}
printf("Signaling all threads to free up all resources and exit...\n");
/* Update exit_flag, and wait 1 sec just in case, to give all threads time to close */
exit_flag = TRUE;
sleep(1);
}
和rcv_usr_input句柄代码:
void * rcv_usr_input(void * arg_p)
{
char in_buf[BUFF_SIZE] = {0};
while(FALSE == exit_flag) {
memset(in_buf, 0, BUFF_SIZE);
if(NULL == fgets(in_buf, BUFF_SIZE, stdin)) {
error("fgets() failed! errno = ");
}
/* No input from the user was received */
if('[=12=]' == in_buf[0]) {
continue;
}
in_buf[0] = tolower(in_buf[0]);
if( ('q' == in_buf[0]) && ('\n' == in_buf[1]) ) {
close_server(USER_EXIT);
} else {
printf("Invalid input!\nType 'q' or 'Q' to exit only\n");
}
}
printf("User Input handler is done\n");
return NULL;
}
我猜我的问题与在主循环结束时加入使用 rcv_usr_input 的线程有关,但我不知道是什么正是导致这种行为的原因。
我很乐意得到一些帮助,谢谢
解释很直接。
fgets(in_buf, BUFF_SIZE, stdin);
该调用会阻塞线程,直到它收到一行输入。也就是说,它不会 return 直到输入换行符或输入 BUFF_SIZE-1
个字符。
因此,即使信号处理程序将 exit_flag
设置为 FALSE
,rcv_usr_input
线程也不会看到它,直到它从 fgets
解除阻塞。当您按下 "return".
根据 http://www.cplusplus.com/reference/cstdio/fgets/,fgets 会阻塞,直到读取了指定数量的字节。
我建议尝试 fread 或其他一些不阻塞的输入接收函数,然后一次只读取一个字节。下面是可以帮助您的示例代码:
if (fread(in_buf, 1,1, stdin) > 0){
//character has been read
}
而且我不会担心你的信号处理程序中的额外睡眠语句,因为它最多会导致强制退出延迟。
Mike 和 Kaylum 正确地识别了 fgets()
阻塞的根本问题。然而,更大的问题仍然存在:当进程收到 SIGINT
时如何终止阻塞线程。有几种解决方案。
特遣队:
一种解决方案是分离阻塞线程,因为分离线程不会阻止进程在最后一个非分离线程终止时终止。通过在其上调用 pthread_detach()
来分离线程,例如,
#include <pthread.h>
// Called by pthread_create()
static void* start(void* arg)
{
pthread_detach();
...
}
或者通过创建具有 PTHREAD_CREATE_DETACHED
属性的线程,例如,
#include <pthread.h>
...
pthread_attr_t attr;
(void)pthread_attr_init(&attr);
(void)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
...
(void)pthread_t thread;
(void)pthread_create(&thread, &attr, ...);
请注意 pthread_join()
应该 而不是 在分离的线程上调用。
信号转发:另一种解决方案是不分离阻塞线程,而是通过pthread_kill()
将SIGINT
之类的信号转发到线程,如果信号有未在阻塞线程上收到,例如,
#include <pthread.h>
#include <signal.h>
...
static pthread_t thread;
...
static void handle_sigint(int sig)
{
if (!pthread_equal(thread, pthread_self()) // Necessary
(void)pthread_kill(thread, SIGINT);
}
...
sigaction_t sigaction;
sigaction.sa_mask = 0;
sigaction.sa_flags = 0;
sigaction.sa_handler = handle_sigint;
(void)sigaction(SIGHUP, &sigaction, ...);
...
(void)pthread_create(&thread, ...);
...
(void)pthread_join(thread, ...);
...
这将导致阻塞函数 return,errno
设置为 EINTR
。
请注意,未指定在多线程进程中使用 signal()
。
线程取消:另一种解决方案是通过pthread_cancel()
取消阻塞线程,例如
#include <pthread.h>
...
static void cleanup(...)
{
// Release allocated resources
...
}
...
static void* start(void* arg)
{
pthread_cleanup_push(cleanup, ...);
for (;;) {
...
// Call the blocking function
...
}
pthread_cleanup_pop(...);
...
}
....
static void handle_sigint(int sig)
{
(void)pthread_cancel(thread);
}
...
sigaction_t sigaction;
sigaction.sa_mask = 0;
sigaction.sa_flags = 0;
sigaction.sa_handler = handle_sigint;
(void)sigaction(SIGHUP, &sigaction, ...);
...
(void)pthread_create(&thread, ..., start, ...);
...
(void)pthread_join(thread, ...);
...
对于在对 select()
或 poll()
的调用中阻塞的线程,还有另一种解决方案:创建一个文件描述符,阻塞函数也在该文件描述符上等待,并在收到适当的请求后关闭该描述符信号——但可以说,该解决方案超出了这个问题的范围。