信号捕获和线程终止的麻烦 - C.

时间:2016-02-20 19:28:31

标签: c multithreading pthreads signals pthread-join

我正在编写一个使用线程的程序,我也希望从用户那里捕获Ctrl + C信号。所以,在我进行多线程之前,我会让信号捕获。

我的主线程(我的意思是除了程序运行的实际主线程),是一个处理用户输入的方法,我也将这个线程加入主程序线程。

问题是,当测试并按Ctrl + C退出程序时, 负责接收用户输入的线程直到我点击键盘上的“返回”才关闭 - 就像它卡在无限循环上一样。

当键入'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('\0' == 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 的线程有关,但我无法弄清楚究竟是什么导致了这种行为。

我很乐意得到一些帮助,谢谢

3 个答案:

答案 0 :(得分:2)

Mike和Kaylum通过fgets()正确地确定了阻塞的根本问题。但是,更大的问题仍然存在:如何在进程收到SIGINT时终止阻塞线程。有几种解决方案。

Thead Detachment: 一种解决方案是分离阻塞线程,因为分离的线程不会阻止进程在最后一个非分离线程终止时终止。通过在其上调用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()应该在分离的线程上调用

信号转发:另一种解决办法不是分离阻塞线程,而是通过SIGINT转发pthread_kill()等信号,如果未在阻塞线程,例如,

#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, ...);
    ...

这将导致阻止功能返回,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()时阻塞的线程还有另一种解决方案:创建一个文件描述符,阻塞函数也会在收到适当的信号后等待并关闭该描述符 - - 但可以说,这个解决方案超出了这个问题的范围。

答案 1 :(得分:1)

解释很简单。

fgets(in_buf, BUFF_SIZE, stdin);

该调用阻塞线程,直到它收到一行输入。也就是说,在输入换行符或输入BUFF_SIZE-1个字符之前,它不会返回。

因此,即使信号处理程序将exit_flag设置为FALSErcv_usr_input线程也不会看到它,直到它从fgets解除阻塞。当你按下“返回”时会发生这种情况。

答案 2 :(得分:1)

根据http://www.cplusplus.com/reference/cstdio/fgets/,fgets会一直阻塞,直到读取了指定的字节数。

我建议尝试fread或其他一些不会阻塞的输入接收函数,然后一次只读取一个字节。以下是帮助您的示例代码:

if (fread(in_buf, 1,1, stdin) > 0){
//character has been read
}

我不担心你的信号处理程序中的额外睡眠声明,因为它会导致最强有力的退出延迟。