2013-09-29 43 views
2

我有一个C程序有两个线程:主线程连续从网络读取数据并将其输出到屏幕上,而辅助线程侦听并处理来自标准输入的按键。如何中断阻塞IO的后台线程?

当前我的程序捕获了SIGINT,SIGTERM和SIGPIPE以便干净地终止程序。我的问题是,在主线程结束时(一旦主循环已从信号处理程序终止),它会尝试使用tcsetattr来恢复终端设置,但是这会阻塞,直到另一个线程上当前的fgetc调用返回。

如何中断后台线程以使fgetc调用返回并且主线程可以恢复终端设置并干净地退出?

我试过使用pthread_kill(thread, SIGINT)但这只是导致我现有的信号处理程序再次被调用。

相关代码:

// If the program should still be running. 
static sig_atomic_t running = 1; 

// Background thread that reads keypresses. 
pthread_t thread; 

static void *get_keypresses(); 

static void receive_signal(int signal) { 
    (void)signal; 
    running = 0; 
} 

int main(int argc, char *argv[]) { 
    // Set up signal handling. 
    if(signal(SIGINT, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGINT.\n"); 
    } 
    if(signal(SIGTERM, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGTERM.\n"); 
    } 
    if(signal(SIGPIPE, receive_signal) == SIG_ERR) { 
     fprintf(stderr, "Error setting signal handler for SIGPIPE.\n"); 
    } 

    // Set up thread attributes. 
    pthread_attr_t thread_attrs; 
    if(pthread_attr_init(&thread_attrs) != 0) { 
     perror("Unable to create thread attributes"); 
     exit(2); 
    } 
    if(pthread_attr_setdetachstate(&thread_attrs, PTHREAD_CREATE_DETACHED) != 0) { 
     perror("Unable to set thread attributes"); 
     exit(2); 
    } 

    // Set up terminal for reading keypresses. 
    struct termios orig_term_attr; 
    struct termios new_term_attr; 
    tcgetattr(fileno(stdin), &orig_term_attr); 
    memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios)); 
    new_term_attr.c_lflag &= ~(ECHO|ICANON); 
    tcsetattr(fileno(stdin), TCSANOW, &new_term_attr); 

    // Start background thread to read keypresses. 
    if((pthread_create(&thread, &thread_attrs, &get_keypresses, NULL)) != 0) { 
     perror("Unable to create thread"); 
     exit(2); 
    } 

    // Main loop. 
    while(running) { 
     // Read data from network and output to screen. 
    } 

    // Restore original terminal attributes. ***IT BLOCKS HERE*** 
    tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr); 

    return 0; 
} 

// Get input from the keyboard. 
static void *get_keypresses() { 
    int c; 
    while(running) { 
     // Get keypress. ***NEEDS TO BE INTERRUPTED HERE*** 
     if((c = fgetc(stdin)) != - 1) { 
      // Handle keypress. 
     } 
    } 
    return NULL; 
} 
+0

这里有一篇文章介绍了一种可以用来中断的方法。这可以通过诸如@HapKom建议的内容之类的标志或类似集合来使用。还建议使用Sleep();在线程工作者函数中完成动作之后,例如。就在你的'// Handle keypress'注释下。 – ryyker

回答

1

更换

if((c = fgetc(stdin)) != -1) 

与线程是否接收到信号

if (0 < read(fileno(stdin), &c, 1)) 

read()将所中断。

2

的一种方式,我看到的是民调阅读标准输入()选择()有一些小超时。当超时返回时,如果设置为“false”值,则可以检查“正在运行”标志并执行清除终止。

可能不是最好的解决方案,但它应该工作。

+1

谢谢。我没有使用这些方法,但是这促使我考虑让fgetc调用非阻塞,然后我找到了解决方案。 – DanielGibbs

1

我设法找到了适合我的解决方案:我从标准输入非阻塞读取。

fcntl(fileno(stdin), F_SETFL, O_NONBLOCK); 

这也需要某种形式的在后台线程sleep(或usleepnanosleep)。

感谢HapKoM让我朝着正确的方向思考。

2

有两种方法去:

  • 你改变你的代码不会阻止(使用O_NONBLOCK,则poll(),选择()等),
  • 你强迫你的受阻代码来获得退出阻塞系统调用。

强制系统调用的结束基本上可以通过两种方式来完成:要么你做的系统调用无法完成(例如,您关闭文件描述符上阻塞调用等待),或者你发送一些发信号给该线程,并确保信号处理已正确设置。正确的信号处理意味着信号不会被忽略,不会被屏蔽,并且信号标志的设置方式使信号处理后系统调用不会重新启动(请参阅sigaction(),SA_RESTART标志)。