如何以编程方式停止从标准输入读取?

时间:2010-11-15 12:22:07

标签: c pthreads stdin eof

当文件描述符上没有数据时,

fgetc()和其他输入函数可以返回。这可以模拟控制台应用程序从键盘上键入Ctrl-D(至少在unix上)读取。但是如何以编程方式进行呢?例如,如何从以下代码中的读者线程中的fgetc()返回(注意:忽略可能的竞争条件)?

#include <pthread.h>
#include <stdio.h>

void* reader()
{
    char read_char;
    while((read_char = fgetc(stdin)) != EOF) {
        ;
    }

    pthread_exit(NULL);
}

int main(void)
{
    pthread_t thread;
    pthread_create(&thread, NULL, reader, NULL);

    // Do something so the fgetc in the reader thread will return

    pthread_exit(NULL);
}

谢谢!

5 个答案:

答案 0 :(得分:3)

当发生某些事件来处理该事件时,您似乎希望线程在fgetc(stdin)上停止阻塞。如果是这种情况,您可以在select()和其他一些消息管道上stdin,以便线程可以处理来自两者的输入:

fd_set descriptor_set
FD_ZERO(&descriptor_set); 
FD_SET(STDIN_FILENO, &descriptor_set); 
FD_SET(pipefd, &descriptor_set); 

if (select(FD_SETSIZE, &descriptor_set, NULL, NULL, NULL) < 0) 
{ 
  // select() error
} 

if (FD_ISSET(STDIN_FILENO, &descriptor_set)) {
  // read byte from stdin
  read(STDIN_FILENO, &c, 1);
}

if (FD_ISSET(pipefd, &descriptor_set)) 
  // Special event. Do something else

另请注意,您的流程中只有一个帖子应该从stdin读取。

答案 1 :(得分:1)

你可以'关闭'标准输入,或者将标准输入连接到'/ dev / null'(Windows上为'NUL:')和freopen(),或者你可以将标准输入连接到'/ dev / zero ”。

如果关闭它,每个函数调用都将失败,因为文件流无效。如果将其连接到空数据源,则所有读取都将失败并立即返回EOF。如果将其连接到零数据源,则每次读取都将成功并返回相应数量的零字节。

其中一种可能足以满足您的需求。如果没有,那么您可能需要向我们提供您实际需要的更详细的解释。

答案 2 :(得分:0)

Alexandre发布了正确的解决方案。他的回答恰恰回应了我提出的问题。它遵循简单的自编译代码,基于他的提示:

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

static int pipe_fds[2];

void* user_interaction()
{
    char read_char;

    fd_set descriptor_set;
    FD_ZERO(&descriptor_set); 
    FD_SET(STDIN_FILENO, &descriptor_set); 
    FD_SET(pipe_fds[0], &descriptor_set);

    while(1)
    {
        if (select(FD_SETSIZE, &descriptor_set, NULL, NULL, NULL) < 0) {
            // select() error
        }

        if (FD_ISSET(STDIN_FILENO, &descriptor_set)) {
            // read byte from stdin
            read(STDIN_FILENO, &read_char, 1);
        }

        if (FD_ISSET(pipe_fds[0], &descriptor_set))
            // Special event. break
            break;
    }

    pthread_exit(NULL);
}

int main(void)
{
    pipe(pipe_fds);

    pthread_t thread;
    pthread_create(&thread, NULL, user_interaction, NULL);

    // Before closing write pipe endpoint you are supposed
    // to do something useful
    sleep(5);

    close(pipe_fds[1]);

    pthread_join(thread, NULL);

    pthread_exit(NULL);
}

答案 3 :(得分:0)

使用POSIX,您可以发信号通知其基元(例如“读取”)正在阻塞的线程,如果您已设置无操作信号处理程序且SA_RESTART位已清除,则基元将因EINTR错误而失败。这是原版的工作版本:

#include <pthread.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static void SignalHandler(int signum)
{
}

void* reader(void *arg)
{
    char read_char;
    while((read_char = fgetc(stdin)) != EOF) {
        ;
    }
    printf("leaving reader\n");
    return NULL;
}

int main(int argc, const char * argv[])
{
    struct sigaction action;
    memset(&action, 0, sizeof(action));  // SA_RESTART bit not set
    action.sa_handler = SignalHandler;
    sigaction(SIGUSR1, &action, NULL);

    pthread_t thread;
    pthread_create(&thread, NULL, reader, NULL);

    sleep(1); // time to start reader thread
    // Do something so the fgetc in the reader thread will return
    pthread_kill(thread, SIGUSR1);
    sleep(1); // time to exit reader thread; could join it if set up

    return 0;
}

答案 4 :(得分:0)

我尝试以稍微修改的形式使用this answer中的代码:

void *user_interaction()
{
    char ch;
    int rv;
    fd_set set;

    FD_ZERO(&set);
    FD_SET(STDIN_FILENO, &set);
    FD_SET(pipe_fds[0], &set);

    while (1)
    {
        rv = select(pipe_fds[0] + 1, &set, NULL, NULL, NULL);
        if (rv < 0)
        {
            printf(">>> select(): error occurred, %d\n", rv);
            break;
        }
        
        if (FD_ISSET(pipe_fds[0], &set))
        {
            printf(">>> pipe_fds[0]: is ready\n");
            break;
        }

        if (FD_ISSET(STDIN_FILENO, &set))
        {
            read(STDIN_FILENO, &ch, 1);
            write(STDOUT_FILENO, &ch, 1);
        }
    }

    pthread_exit(NULL);
}

但是没有得到预期的行为。如下所示执行时:
$ echo -n 1 | ./a.out

我的终端在无限循环中使用1进行渲染,select()从未报告该管道准备就绪(即即使在主线程中close()将其准备就绪) )。

通过一些实验,我发现您需要在循环内移动FD_ZERO / FD_SET,以使select()能够按预期工作:

void *user_interaction()
{
    char ch;
    int rv;
    fd_set set;

    while (1)
    {
        FD_ZERO(&set);
        FD_SET(STDIN_FILENO, &set);
        FD_SET(pipe_fds[0], &set);

        rv = select(pipe_fds[0] + 1, &set, NULL, NULL, NULL);
        if (rv < 0)
        {
            printf(">>> select(): error occurred, %d\n", rv);
            break;
        }
        
        if (FD_ISSET(pipe_fds[0], &set))
        {
            printf(">>> pipe_fds[0]: is ready\n");
            break;
        }

        if (FD_ISSET(STDIN_FILENO, &set))
        {
            read(STDIN_FILENO, &ch, 1);
            write(STDOUT_FILENO, &ch, 1);
        }
    }

    pthread_exit(NULL);
}