3

我有一个带有两个线程的 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

4 回答 4

3

有两种方法:

  • 您将代码更改为不阻塞(使用 O_NONBLOCK、poll()、select() 等),
  • 你强制你被阻塞的代码被踢出阻塞系统调用。

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

于 2013-09-30T01:25:30.567 回答
2

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

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

于 2013-09-29T22:44:20.927 回答
2

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

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

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

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

于 2013-09-29T23:14:02.607 回答
1

代替

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

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

read()如果线程收到信号,将被中断。

于 2013-09-30T10:04:18.813 回答