2016-02-20 36 views
0

我正在编写一个使用线程的c程序,并且我还希望捕获用户的Ctrl + C信号。所以,在我开始多线程之前,我让信号捕捉。信号捕获和线程终止的问题 - 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我的主循环结束的线程,但我无法弄清楚究竟是什么造成这种行为。

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

+0

通过信号处理程序(哦,男孩!混合线程和信号,*可能出错*),未定义的行为写入到'exit_flag'的非同步访问的数据竞争。 – EOF

+2

@EOF不需要讽刺,使用线程和信号对我来说是新的,而且我仍然在学习:) 如何在使用线程时处理信号?我总是很高兴学到新东西 – Adiel

+0

该代码有几个问题,你可以在这个网站上研究。 'pthread_create'返回一个错误代码(它不会设置'errno')。 'exit_flag'可能需要'volatile sig_atomic_t',但这不会帮助threads_之间的可见性问题。 'printf'不是异步信号安全的。当错误_and_成功(即EOF)时,'fgets'可以返回NULL。 – pilcrow

回答

1

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

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

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

而且我也不会担心你的信号处理额外的睡眠声明,因为它会导致延误有力充其量退出。

+0

感谢您的评论。 我使用fgets的原因是为了覆盖用户输入的场景,例如456个字符(在键盘上睡了一分钟可以说)。 fread将不会涵盖我假设的这种情况,因此如果我只读取一个字符,可以说用户将输入10个字符,其他9个字符将等待从标准输入读取,在下一次运行中,不是吗? 请纠正我,如果我错了,还 - 如果我是正确的,我会很高兴为解决方案类型的描述 谢谢! – Adiel

+0

当检测到字符时,将其存储到缓冲区中并递增缓冲区指针,直到达到需要的字符数以进行处理。此外,您可以在您的程序中实现定时器,并在经过一段时间后运行一个功能。 – Mike

+0

'fread'将无法正常工作,因为它几乎与'fgets'完全相同的问题。它会一直阻塞,直到它读取输入。相反,通常的做法是使用一个函数,比如'read',当信号发生时它会中止。然后你看'errno'来看看它是否是'EINTR'。 – kaylum

1

解释很简单。

fgets(in_buf, BUFF_SIZE, stdin); 

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

因此,即使信号处理程序将exit_flag设置为FALSErcv_usr_input线程也不会看到该线程,直到它从fgets解锁。当你按下“返回”时会发生什么。

+0

感谢您的评论,请参阅我的评论给迈克,如果你能帮助,那将是伟大的:)谢谢! – Adiel

2

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()应该被一种叫做分离线程。

的信号转发:另一种解决方案是不脱离阻塞线程,但如果该信号没有被阻塞的线程上接收到一样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()呼叫阻塞线程又一解决方案:创建一个文件描述符阻塞函数也等待并在接收到适当的信号时关闭该描述符 - 但这种解决方案可以说超出了这个问题的范围。