【问题标题】:Cancelling getchar()取消 getchar()
【发布时间】:2012-07-16 22:34:48
【问题描述】:

我有一个执行大量处理的小程序。您可以按回车键打印进度。

我实现这一点的方式是在主线程中完成处理,同时我有一个 pthread 在 getchar() 上不断循环以等待输入键。

问题是当我完成处理时。当这种情况发生时,主线程结束,但仍然等待输入,因为 getchar() 被阻塞。

如何“取消”getchar()?

【问题讨论】:

  • 您应该使用基于事件的键盘输入或定期增加进度,而不是使用 getchar()
  • 你可以exit()吗?

标签: c pthreads getchar


【解决方案1】:

我能想到的最便携的解决方案是:

  • 使用pipe() 构造两个FD,一个是读取器,另一个是写入器。将读者交给您的read() 循环;将作者交给需要终止读者的人。
  • 使用读取线程中的select() 等待标准输入和读取器管道的可读性。
  • 如果标准输入变得可读,则读取一个字符,对其进行处理,然后重新启动循环。
  • 如果读取器管道变得可读,请将其关闭并终止循环。

现在,您需要做的就是关闭管道的另一端,这会将阅读器线程从其select() 中唤醒,然后它应该终止。

传统方法涉及使用信号,但这种基于管道的解决方案允许您检查标准输入上的输入以及检查是否应使用相同的轮询机制终止。


请注意,混合getchar()select() 将不起作用,因为getchar() 将在后台有效地使用fread(),并且fread() 执行的缓冲可能会导致select() 阻塞,即使有可用的数据。请改用read()。这是我用来测试这种方法的示例程序。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/select.h>

void * entry_point(void * p) {
    int readpipe = *(int *)p;
    fd_set rfds;

    char c;

    for (;;) {
        FD_ZERO(&rfds);
        FD_SET(STDIN_FILENO, &rfds);
        FD_SET(readpipe, &rfds);

        while (select(readpipe + 1, &rfds, NULL, NULL, NULL) == 0);

        if (FD_ISSET(readpipe, &rfds)) {
            close(readpipe);
            break;
        }

        if (FD_ISSET(STDIN_FILENO, &rfds)) {
            if (read(STDIN_FILENO, &c, sizeof(c)) > 0) {
                printf("Read: %d\n", c);
            }
        }
    }

    printf("Thread terminating\n");

    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    int r;
    int pipes[2];

    pipe(pipes);

    if (r = pthread_create(&thread, NULL, entry_point, &pipes[0])) {
        printf("Error: %d\n", r);
        return 1;
    }

    sleep(5);

    printf("Closing pipe and joining thread.\n");

    close(pipes[1]);
    pthread_join(thread, NULL);

    pthread_exit(NULL);
}

示例运行:

$ time ./test
1
Read: 49
Read: 10
2
Read: 50
Read: 10
3
Read: 51
Read: 10
4
Read: 52
Read: 10
5
Read: 53
Read: 10
Closing pipe and joining thread.
Thread terminating

real    0m5.004s
user    0m0.004s
sys     0m0.000s

【讨论】:

  • 请注意,如果标准输入是一个即将到来的终端,它通常是行缓冲的,所以标准输入在用户按下回车之前根本看不到任何数据。解决方案是使用诸如 ncurses 之类的库,即使 stdin 是终端,它也可以抓取单个按键。
  • getchar()read() 仍然在行缓冲上下文中工作,您只需将数据全部放在一个块中即可。我不确定这个基本逻辑会有什么不同。事实上,它应该也能正常工作。
  • 是的,它们仍然有效,但如果用户输入A B C D &lt;ENTER&gt; 的速度很慢,每次按键之间的延迟很长,那么第一次调用getchar()read(..., 1) 将不会返回,直到用户按下&lt;ENTER&gt;,此时他们将返回'A'。所以输入不能立即提供给程序。
  • @AdamRosenfield 这不是 OP 试图解决的问题,它与这个问题相切。如果getchar() 到目前为止工作正常,那么我提供的示例代码也可以工作。无论如何,在用户完成数据之前,程序可能不想要数据。如果他们按退格键怎么办?将线路处理留给 shell 可能正是 OP 想要的。
  • 我同意这与原始问题无关,但它仍然是需要注意的重要说明。
【解决方案2】:

假设你想走这条路,你可以使用 pthread_kill 强制你的 getchar 线程退出。见Kill Thread in Pthread Library。不过,这也不是最佳编程实践。

【讨论】:

  • 这属于“可行,但做起来很糟糕”的类别,所以我不能投票。
  • 我同意,但是 pthreads 中的信号处理在未来可能对 OP 有用。
【解决方案3】:

如果有一个共享变量呢?然后在每次 getchar() 返回后,它会获取一个锁,然后检查是否设置了这个共享变量。如果没有,那么它会执行您所做的任何事情,如果已设置,则线程退出。这样,当 main 退出时,它会获取一个锁,设置变量,释放锁,打印到标准输入,然后等待另一个线程退出。有点复杂,但它应该工作。 〜本

【讨论】:

    【解决方案4】:

    使用 printf() 将一个字符写入标准输入。

    【讨论】:

    • 我也尝试过这种黑客攻击。但是 stdin 是只读流,因此在尝试写入 stdin 时会失败并返回 EOF。
    【解决方案5】:

    我真的很喜欢关闭标准输入的想法。这样做很好,可能会奏效。

    以下关闭标准输入:close(0); fclose(标准输入);关闭(STDIN_FILENO);守护进程(0, 0);

    您也可以从主线程向标准输入写入特殊的字符序列或 EOF,然后将其关闭。

    【讨论】:

    • 我正在试验,遗憾的是我无法让这种方法发挥作用。那好吧。 :(
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    相关资源
    最近更新 更多