如何中断在IO上阻塞的后台线程?

时间:2013-09-29 22:35:55

标签: c pthreads interrupt

我有一个带有两个线程的C程序:主线程连续从网络读取数据并将其打印到屏幕上,而辅助线程则监听并处理来自标准输入的按键。

目前我的程序捕获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;
}

4 个答案:

答案 0 :(得分:2)

我看到的一种方法是通过 poll() select()读取标准输入,并使用一些小的超时。当它在超时时返回时,您可以检查"运行"标志并执行清除终止,如果它被设置为" false"值。

可能它不是最佳解决方案,但它应该有效。

答案 1 :(得分:2)

有两种方法:

  • 您更改代码不会阻止(使用O_NONBLOCK,poll(),select()等),
  • 您强制阻止的代码被阻止系统调用。

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

答案 2 :(得分:1)

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

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

这还需要在后台线程中使用某种形式的sleep(或usleepnanosleep)。

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

答案 3 :(得分:1)

替换

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

if (0 < read(fileno(stdin), &c, 1)) 
如果线程收到信号,

read()将被中断。

相关问题