How to programmatically stop reading from stdin?
fgetc() and other input functions can return when there's no data on the file descriptor. This can be simulated for console applications reading from stdin typing Ctrl-D on keyboard (at least on unix). But how to do it programmatically? For example, how to return from the fgetc() in the reader thread in the following code (NB: ignore the possible race condition)?
#include <pthread.h>开发者_Python百科
#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);
}
Thanks!
It seems you want a threads to stop blocking on fgetc(stdin)
when some event occurs to handle that event instead. If that's the case you could select()
on both stdin
and some other message pipe so that the thread can handle input from both:
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
Also note that only one thread in your process should be reading from stdin
.
You can either 'close' standard input, or connect standard input to '/dev/null' ('NUL:' on Windows) with freopen()
, or you can connect standard input to '/dev/zero'.
If you close it, every function call will fail because the file stream is not valid. If you connect it to the null data source, all reads will fail and return EOF immediately. If you connect it to the zero data source, every read will succeed and return a corresponding number of zero bytes.
It is possible one of those will suit your needs sufficiently. If not, then you probably need to give us a more detailed explanation of what you actually need.
With POSIX you can signal the thread whose primitives (e.g. "read") are blocking, and if you have set up a do-nothing signal handler with the SA_RESTART bit cleared then the primitives will fail with EINTR errors. Here's a working version of the original:
#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;
}
Alexandre posted the correct solution. His answer respond precisely to the question I asked. It follows simple self compiling code based on his hints:
#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);
// Re-set the selected file descriptor so it can
// be signaled again
FD_SET(STDIN_FILENO, &descriptor_set);
}
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);
}
I tried to use the code from this answer in a slightly modified form:
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);
}
but wasn't getting the expected behaviour. When executed like in the below:
$ echo -n 1 | ./a.out
my terminal was being rendered with 1
's in the infinite loop and the pipe was never reported by select()
to be ready (i.e. even after close()
ing it in the main thread).
With some experimentation, I figured that you need to move FD_ZERO
/FD_SET
inside the loop, to get select()
to work as desired:
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);
}
精彩评论