簡體   English   中英

信號捕獲和線程終止有麻煩-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('\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的線程有關,但是我無法弄清楚到底是什么導致了此行為。

我很高興得到一些幫助,謝謝

Mike和Kaylum已經正確地確定了fgets()阻止的基本問題。 但是,更大的問題仍然存在:當進程收到SIGINT時如何終止阻塞線程。 有幾種解決方案。

Thead分離:一種解決方案是分離阻塞線程,因為分離的線程不會阻止進程在最后一個非分離的線程終止時終止。 可以通過在線程上調用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, ...);
    ...

這將導致阻塞函數在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()阻塞的線程還有另一種解決方案:創建一個文件描述符,阻塞函數也將在該文件描述符上等待,並在接收到適當的信號后關閉該描述符-但是,可以說該解決方案是有爭議的,超出了此問題的范圍。

解釋很簡單。

fgets(in_buf, BUFF_SIZE, stdin);

該調用將阻塞線程,直到接收到輸入行。 也就是說,它直到輸入換行符或輸入BUFF_SIZE-1字符BUFF_SIZE-1返回。

因此,即使信號處理程序將exit_flag設置為FALSErcv_usr_input線程也不會看到它,直到它從fgets rcv_usr_input阻止為止。 當您按下“返回”時會發生這種情況。

根據http://www.cplusplus.com/reference/cstdio/fgets/,fgets會阻塞,直到讀取了指定的字節數為止。

我建議嘗試讀取或其他不會阻塞的輸入接收功能,然后一次只讀取一個字節。 以下示例代碼可為您提供幫助:

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

而且我不會擔心信號處理程序中額外的sleep語句,因為它最多會導致強制退出的延遲。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM