3

I've got a small program that does a large amount of processing. The progress of which you can get a print of by hitting the enter key.

The way I've implemented this is by having the processing done in the main thread whilst I have a pthread constantly looping on getchar() to wait for the enter key.

The problem is when I have finished with the processing. When this happens the main thread finishes, but still waits for enter to be pressed because getchar() is blocking.

How do I "cancel" getchar()?

rhlee
  • 3,857
  • 5
  • 33
  • 38

5 Answers5

4

The most portable solution I can think of is:

  • Use pipe() to construct two FDs, one a reader and the other a writer. Give the reader to your read() loop; give the writer to whoever needs to terminate the reader.
  • Use select() from the read thread to wait for readability of both stdin and the reader pipe.
  • If stdin becomes readable, read a character, process it, and then restart the loop.
  • If the reader pipe becomes readable, close it and terminate the loop.

Now, all you should have to do is close the other end of the pipe and this will wake up the reader thread out of its select() and it should then terminate.

The traditional approach involves using signals, however this pipe-based solution allows you to check for input on stdin as well as check if you should terminate using the same polling mechanism.


Note that mixing getchar() and select() will not work, since getchar() will effectively use fread() under the hood, and the buffering performed by fread() can cause select() to block even though there is data available. Use read() instead. Here is an example program I used to test this approach.

#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);
}

Example run:

$ 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
cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Note that if stdin is a coming terminal, it's typically line-buffered, so stdin won't see any data at all until the user presses enter. The solution to that is to use a library such as ncurses which can grab individual keypresses even when stdin is a terminal. – Adam Rosenfield Jul 16 '12 at 23:08
  • `getchar()` and `read()` still work in a line-buffered context, you just get the data all in one chunk. I'm not sure how this basic logic would differ; in fact it should work just as well. – cdhowie Jul 16 '12 at 23:11
  • Yes, they still work, but if the user very slowly types `A B C D ` with a long delay between each keypress, then the first call to `getchar()` or `read(..., 1)` won't return until the user presses ``, at which point they'll return `'A'`. So the input is not immediately available to the program. – Adam Rosenfield Jul 16 '12 at 23:13
  • @AdamRosenfield That is not the problem that the OP is trying to solve, and it is tangential to this question. If `getchar()` has been working fine so far, then the example code I have supplied will work too. And anyway, the program may not want the data until the user is done with it. What if they press backspace? Leaving the line processing to the shell may be exactly what the OP wants. – cdhowie Jul 16 '12 at 23:14
  • I agree that it's tangential to the original problem, but it's still an important note to be aware of. – Adam Rosenfield Jul 16 '12 at 23:16
  • Thank you so much for your detailed answer cdhowie. I was halfway implementing your answer when my program started to quit normally. It turns out that this because I switched from getchar() to read(). I think this may be because read() can be interrupted by signals whereas getchar() cannot. Unfortunately I didn't use the select() multiplexing code that you supplied as my program was now "behaving correctly" . However your answer did teach me more about file descriptors and pipes and I'm sure to find it useful in the future. – rhlee Jul 17 '12 at 18:02
0

Assuming you want to go down this path, you can use pthread_kill to force your getchar thread to exit. See Kill Thread in Pthread Library. Again, not the best programming practice though.

Community
  • 1
  • 1
ryanbwork
  • 2,123
  • 12
  • 12
0

What about having a shared variable. then after every getchar() returns it grabs a lock, then checks if this shared variable is set. If not then it does whatever you have it doing, if it is set then the thread exits. This way when main goes to exit it grabs a lock, sets the variable, releases the lock, prints to stdin, and waits for the other thread to exit. A little complicated, but it should work. ~Ben

Ben
  • 669
  • 8
  • 14
-1

Write a character to stdin using printf().

  • I also try this hacking. But stdin is read-only stream, so it fail and return EOF when try to write to stdin. – liuyang1 Feb 11 '18 at 07:33
-1

I really like the closing stdin idea. This is fine to do and may work.

The following closes stdin: close(0); fclose(stdin); close(STDIN_FILENO); daemon(0, 0);

You could also just write a special sequence of characters or maybe EOF to stdin, from the main thread, and get it to shutdown.

Ben
  • 669
  • 8
  • 14