4

I am developing a Windows application that has a separate thread for processing user (or 3rd party) application input via stdin.

This thread is designed such that it waits via WaitForMultipleObjects on two events:

  1. A death signal. When this signal is raised, the interface-processing thread shuts down.
  2. An interface signal. When this signal is raised, there is input ready to be read. The input is read and processed.

Under Windows this thread enters a main loop where it Waits for these 2 events (where bWaitAll is FALSE). Waiting on the stdin handle has the effect of signaling when there is input ready to be read, and the other event is set from elsewhere in the application.

This works exactly as I want. It waits for an event to be raised without entering in to a busy-wait, and it waits for both event simutaneously.

I wish to port this functionality to Linux, but I'm not sure how to achieve the desired result. Fundamentally, what I really want is this:

Under Linux, how do I design a thread so that it will respond immediately to user-input on stdin, yet it can also respond immediately to a kill-flag being raised from elsewhere in the application?

In order to accomplish the latter, it seems to me that I cannot use cin, gets, getch or any other function that blocks until the user has entered text. Yet I do not know how to read user input in a console-based application without blocking.

I'm open to any change in architecture (if there's a more Linux-y way to do this) that include having user input processed in a separate thread that can be terminated from elsewhere in the application. I'm using GCC 4.4, and Boost 1.51.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • I think ncurses might have non-blocking IO. – chris Nov 06 '12 at 21:48
  • Some version of "kbhit()" will do the job. Here's a couple implementations: http://cboard.cprogramming.com/c-programming/63166-kbhit-linux.html – Mark Stevens Nov 06 '12 at 21:48
  • The "kill signal" is not a POSIX `signal` but is instead some kind of semaphore? Or any arbitrary variable/memory location being written to? – Brian Cain Nov 06 '12 at 21:50
  • 1
    Ultimately, the answer is likely `select` (or `select` "plus"). BTW, similar to this question - http://stackoverflow.com/a/5525119/489590 – Brian Cain Nov 06 '12 at 21:54
  • further to the suggestions, you could use condition variables : http://en.cppreference.com/w/cpp/thread/condition_variable which may well be the generic way to do what you want in terms of signalling. – Caribou Nov 06 '12 at 21:56
  • 1
    The standard solution on Linux would be not to use multiple threads. The main loop would use `select` on `stdin` and be interrupted by a UNIX signal. – Alexandre C. Nov 06 '12 at 22:01
  • @AlexandreC.: Can I not 'send' a UNIX signal to another thread? My main thread is already doing a bunch of other stuff. – John Dibling Nov 06 '12 at 22:06
  • @MarkStevens: Correct me if I'm wrong, but wouldn't using `kbhit` either result in a busy-wait or a block on `kbhit`? – John Dibling Nov 06 '12 at 22:08
  • @BrianCain: It's actually a messaging mechanism of my own design based on a threadsafe priority queue. At the bottom of it all however is a condition variable. – John Dibling Nov 06 '12 at 22:09
  • @chris: I actually looked at ncurses, but the documentation scared me. I may take another look. – John Dibling Nov 06 '12 at 22:10
  • @JohnDibling: Unfortunately, threads and UNIX signals don't play very well together. There are many scary problems that can arise (eg. what if a signal pops up but a mutex is locked). If you show us more structure, perhaps we can tell you more potential designs. – Alexandre C. Nov 06 '12 at 22:11
  • 1
    @JohnDibling: See also [`eventfd(2)`](http://www.kernel.org/doc/man-pages/online/pages/man2/eventfd.2.html) as an alternative to signals if you're going the `select` + threads route. – Alexandre C. Nov 06 '12 at 22:13
  • @AlexandreC.: I will, thanks. I'll also edit to give more details about my design, though that might take me a little while. – John Dibling Nov 06 '12 at 22:15
  • In the meantime, if you feel like swimming through code, this application is in my githib. Check out `processor.cpp` in the 0.9 branch. Implementation of the message queue is in `msg.h:217`. https://github.com/jdibling/xcast – John Dibling Nov 06 '12 at 22:21

2 Answers2

3

The standard way of doing this in Linux is to use the select(2) system call. However, select is more limited than WaitForMultipleObjects, in that it can only wait on file descriptors, not other kinds of objects (such as events). So, the typical way of working around that is to create a pipe and write a dummy value to the pipe as your "signal".

Something like this:

// Error checking omitted for expository purposes
int pipefd[2];
pipe(pipefd);  // Create the pipe

while(1)
{
    // Create file descriptor set of stdin and the read end of the pipe
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(STDIN_FILENO, &fds);
    FD_SET(pipefd[0], &fds);
    int maxfd = MAX(STDIN_FILENO, pipefd[0]);

    // Wait until input becomes available on either stdin or the pipe
    int num_available = select(&fds, NULL, NULL, NULL);

    // Read & process stdin if possible (will not block)
    if (FD_ISSET(STDIN_FILENO, &fds))
    {
        int n = read(STDIN_FILENO, buffer, size);
        ...
    }

    // Read & process pipe if possible (will not block)
    if (FD_ISSET(pipefd[0], &fds))
    {
        char dummy;
        read(pipefd[0], &dummy, 1);
        // Handle signal (e.g. break out of loop)
    }
}

Then to signal to the thread that it's done, just write a single byte to the write end of the pipe:

char dummy = 42;
write(pipefd[1], &dummy, 1);
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • `select()` does only wait on file descriptors, but that's not as much of a problem as it might seem because of the 'everything* is a file' Unix philosophy. For example one way to do events on Linux is `eventfd()`. (*more like 'many things'. plan9 takes 'everything is a file' further.) – bames53 Nov 06 '12 at 22:04
  • 1
    @bames53: Yes, `eventfd` is pretty useful. If you're only targeting Linux specifically, it's a good choice, but otherwise it's not as portable. The `pipe` method is portable to all POSIX systems. – Adam Rosenfield Nov 06 '12 at 22:16
  • @AdamRosenfield: My application is targeted to Windows and Linux, as it's a internal tool. I already wrote the Windows-specific interface stuff, so a Linux-only implementation for handling the interface would be just fine. By the way, +1. – John Dibling Nov 06 '12 at 22:19
1

libev (and several similar incarnations) offers a convenient abstraction around select including being able to pend on signals.

If you have the option to alter the origin of "An interface signal" then you could consider changing it to use raise instead.

Brian Cain
  • 14,403
  • 3
  • 50
  • 88
  • 1
    @JohnDibling, you might be surprised at how many code authors become convinced that revisiting code that they've written is out-of-scope. – Brian Cain Nov 06 '12 at 22:13