【问题标题】:How do I interrupt a background thread that's blocking on IO?如何中断阻塞 IO 的后台线程?
【发布时间】:2013-10-05 17:15:59
【问题描述】:

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

【问题讨论】:

  • 这里有一篇文章描述了一种可以用来打断的方法。这可以与@HapKom 建议的标志或类似设置结合使用。还建议使用 Sleep();在您完成线程工作函数中的操作后,例如。就在您的// Handle keypress 评论下方。

标签: c pthreads interrupt


【解决方案1】:

替换

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

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

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

【讨论】:

    【解决方案2】:

    有两种方法:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

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

      【讨论】:

        【解决方案4】:

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

        也许这不是最好的解决方案,但它应该可以工作。

        【讨论】:

        • 谢谢。我没有使用这些方法,但这促使我考虑让 fgetc 调用非阻塞,然后我找到了解决方案。
        猜你喜欢
        • 2014-01-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-17
        • 2015-06-25
        相关资源
        最近更新 更多