views:

65

answers:

5

I'm running a multi-threaded C program (process?) , making use of semaphores & pthreads. The threads keep interacting, blocking, waking & printing prompts on stdout continuously, without any human intervention. I want to be able to exit this process (gracefully after printing a message & putting down all threads, not via a crude CTRL+C SIGINT) by pressing a keyboard character like #.

What are my options for getting such an input from the user?

What more relevant information could I provide that will help to solve this problem?

Edit: All your answers sound interesting, but my primary question remains. How do I get user input, when I don't know which thread is currently executing? Also, semaphore blocking using sem_wait() breaks if signalled via SIGINT, which may cause a deadlock.

A: 

You would have a thread listening for keyboard input, and then it would join() the other threads when receiving # as input.

Another way is to trap SIGINT and use it to handle the shutdown of your application.

karlphillip
Sorry, I don't understand, if the keyboard thread joins a running thread, then the keyboard thread will wait until the running thread terminates, right? So how will joining a running thread tell it to die? Maybe I misunderstand the join semantics...
anjruu
Could you elaborate on your first method please?
Kedar Soparkar
@anjruu He left out the bit about setting a global flag that all threads are watching. When each notices the flag is set, it should gracefully clean up and exit.
Jonathan
A: 

The way I would do it is to keep a global int "should_die" or something, whose range is 0 or 1, and another global int "died," which keeps track of the number of threads terminated. should_die and died are both initially zero. You'll also need two semaphores to provide mutex around the globals.

At a certain point, a thread checks the should_die variable (after acquiring the mutex, of course). If it should die, it acquires the died_mutex, ups the died count, releases the died_mutex, and dies.

The main initial thread periodically wakes up, checks that the number of threads that have died is less than the number of threads, and goes back to sleep. The main thread dies when all the other threads have checked in.

If the main thread doesn't spawn all the threads itself, a small modification would be to have "threads_alive" instead of "died". threads_alive is incremented when a thread forks, and decremented when the thread dies.

In general, terminating a multithreaded operation cleanly is a pain in the butt, and besides special cases where you can use things like the semaphore barrier design pattern, this is the best I've heard of. I'd love to hear it if you find a better, cleaner one.

~anjruu

anjruu
Instead of a "died" variable, it would be better to use `pthread_join` to wait for threads to exit. This is still annoying though.
jilles
A: 

In general, I have threads waiting on a set of events and one of those events is the termination event.

In the main thread, when I have triggered the termination event, I then wait on all the threads having exited.

Blank Xavier
+1  A: 

SIGINT is actually not that difficult to handle and is often used for graceful termination. You need a signal handler and a way to tell all the threads that it's time to stop. One global flag that threads check in their loops and the signal handler sets might do. Same approach works for "on user command" termination, though you need a way to get the input from the terminal - either poll in a dedicated thread, or again, set the terminal to generate a signal for you.

The tricky part is to unblock waiting threads. You have to carefully design the notification protocol of who tells who to stop and what they need to do - put dummy message into a queue, set a flag and signal a cv, etc.

Nikolai N Fetissov
Global flags (`volatile sig_atomic_t`) set in signal handlers are only valid in single-threaded processes. In multi-threaded processes, you are accessing the flag without proper synchronization (none of the pthread mutex and cond functions are async-signal-safe and calling them from a signal handler may cause deadlock). Also, the need to check the flag explicitly tends to cause poor response times and unnecessarily high power usage (due to wakeups which disturb deep sleep states).
jilles
Yes, true. `volatile` is not the proper vehicle for synchronization. But in practice you never need real-time reaction to a shutdown command. The value of the global flag will propagate to memory *eventually*. That's enough for some thread to pick that up and initiate unwind for all others. And I didn't say to use CVs from a signal handler, and you do need to check *something* after a wakeup :)
Nikolai N Fetissov
+1  A: 

There is no difference in reading standard input from threads except if more than one thread is trying to read it at the same time. Most likely your threads are not all calling functions to read standard input all the time, though.

If you regularly need to read input from the user you might want to have one thread that just reads this input and then sets flags or posts events to other threads based on this input.

If the kill character is the only thing you want or if this is just going to be used for debugging then what you probably want to do is occasionally poll for new data on standard input. You can do this either by setting up standard input as non-blocking and try to read from it occasionally. If reads return 0 characters read then no keys were pressed. This method has some problems, though. I've never used stdio.h functions on a FILE * after having set the underlying file descriptor (an int) to non-blocking, but suspect that they may act odd. You could avoid the use of the stdio functions and use read to avoid this. There is still an issue I read about once where the block/non-block flag could be changed by another process if you forked and exec-ed a new program that had access to a version of that file descriptor. I'm not sure if this is a problem on all systems. Nonblocking mode can be set or cleared with a 'fcntl' call.

But you could use one of the polling functions with a very small (0) timeout to see if there is data ready. The poll system call is probably the simplest, but there is also select. Various operating systems have other polling functions.

#include <poll.h>

...
/* return 0 if no data is available on stdin.
        > 0 if there is data ready
        < 0 if there is an error
*/
int poll_stdin(void) {
    struct pollfd pfd = { .fd = 0, .events = POLLIN };

    /* Since we only ask for POLLIN we assume that that was the only thing that
     * the kernel would have put in pfd.revents */
    return = poll(&pfd, 1, 0);
}

You can call this function within one of your threads until and as long as it retuns 0 you just keep on going. When it returns a positive number then you need to read a character from stdin to see what that was. Note that if you are using the stdio functions on stdin elsewhere there could actually be other characters already buffered up in front of the new character. poll tells you that the operating system has something new for you, not what C's stdio has.

If you are regularly reading from standard input in other threads then things just get messy. I'm assuming you aren't doing that (because if you are and it works correctly you probably wouldn't be asking this question).

nategoose
+1: Great answer!
Kedar Soparkar