Ronald McBean
Ronald McBean

Reputation: 1427

How to signal an event to several threads

How can I send an event to several running threads of my application?

For example: My main thread wants to signal all running threads that the application will exit.

I'm a little confused which of those possibilities leads to an easy and reliable solution:

Could you point a beginner into the right direction and perhaps loose some words about the possiblities above I provided.

Added: - OS is Windows XP

Upvotes: 3

Views: 3800

Answers (4)

kfmfe04
kfmfe04

Reputation: 15327

If you are simply testing for a simple change in state (when to exit), a condition variable is sufficient. Just make sure you find a good WinAPI example to follow: you almost certainly need test the state multiple times (unlike what your intuition may tell you).

If you intend to share tasks/events between threads, a good pattern is to use a producer-consumer pattern, based on a mutexed queue and a condition variable. A typical example is, you have one producer thread that produces jobs which are pushed onto the queue (eg write data to a different file per task). Then, you can have multiple consumer threads which will remove the task from the queue and act on them. To communicate between the threads, you can have a condition variable which will signal to the consumer threads that the mutexed queue may be empty. (Read up on condition variables to understand why I say "may" here).

As you do more and more thread programming, you will look for opportunities where mutexes are unnecessary. For example, if I have a ton of read-only data on disk and I want your threads to calculate on this data, you can read the data before spawning your threads. All your threads will then have access to this data without having to use locks, as long as they behave and do not write to the data. You will find that you implement many thread patterns with just mutexes and condition variables (part of the reason while the POSIX thread library is so small).

One last note: it's a bit out of the scope of your question, but you will also get a feel of how many threads you really need to spawn. Context-switches between threads are relatively cheap, but the costs is definitely non-zero. That means there is a point of diminishing returns when spawning more threads. Just think about it in terms of the OS: the algorithm must spend cycles to decide when to context switch in addition to actually do the switch. This cost is not zero. So another type of optimization you will find as you do more thread-programming is, when to do as much work as you can in the thread without waiting for another thread to do the work. Of course, you must strike a balance between cleanliness of your design and optimizing for speed. It's just something to think about as you design your application.

Upvotes: 2

You have two different problems here. The first one is how to make a bit of data available to different parts of the code, so that the different threads can read it, while the second one is how to modify a bit of state in a thread safe way so that the threads can act on it.

To make a bit of data available to the different threads you can use different approaches:

  • If you have references to the objects that manage the threads, you can use those to manually walk the list of threads and notify each one
  • You could use boost::signal at some point in the program to manage those references (i.e. create an exit signal, and connect all threads on construction.
  • If you don't have those references, you can set the exit flag as a global variable and have the threads read from it

I tend to prefer each thread to be managed by a single object, in which case I would try to move the bit of data to that manager and avoid the global. In most cases the design establishes dependencies and ownership of the resources, in which case you would not need the signals mechanism, as you can walk those dependencies.

The second part of the problem is how to, in a thread safe way, pass the data from the main thread to the rest of the threads, and in this particular case a single bit of data that has one value during most of the program and is set to a different fixed value at one point. Whether this is a global variable or part of the state of the thread manager does not really matter:

  • Mutex. Each thread locks before reading/writing ensuring that the modifications are safe
  • Atomic operations. For a small enough data type, the threading library/OS offers atomic operations that will add the appropriate memory barriers to ensure the data is moved across

Combining both what you have is either a global or a local variable that will be modified either directly (global) or through function calls triggered from the main thread, either directly or through some signals mechanism. The modification of the flag must be done either with atomic operations (introducing memory fences) or by holding a mutex.

If the threads can be inactive (waiting on a condition variable), then you probably want to avoid the global, call the functions, lock on the mutex and inside the mutex both change the flag and use the condition variables to wake the waiting thread. The waiting thread would be responsible for checking the flag whenever it comes back from waiting in a condition.

Upvotes: 2

Len Holgate
Len Holgate

Reputation: 21616

Given you want to "signal all running threads that the application will exit" a manual reset even would do the trick.

You need to have each thread check the event occasionally and exit if the event has been signalled.

You then simply signal the event and wait for the threads to complete if you need to.

It's more complex if you need to do this more than once as a manual reset event must be reset manually and you'd need more code to make sure that every thread has received the notification before you reset the event for use in subsequent notifications, but given your question, this will work just fine.

Upvotes: 2

Emilio Garavaglia
Emilio Garavaglia

Reputation: 20730

  1. boost::condition_variable anticipate std::condition_variable of C++11. As of today (2011) is implemented fully on POSIX interfaced systems (like UNIX / LINUX) but only mimicked on windows (with partial support)
  2. windows CONDITION_VARAIBLE is the "hardware" that is needded to implement std::condition_varable in windows with full support. But exists only from win6 (Vista and 2008). The world is still poulated by a lot of XP (that's why mingw -pthread is not supported in windows).
  3. Winapi evvent objects (see CreateEvent) can mimic a posix condition variable but are not exactly the same (in POSIX a condition variable can be used to weak up just one or all, case by case. in Windows, an Event is created to be automatic (and if signaled wake-up one thread and autoresets) or manual (and if signaled weak up everything waits for it until it will remain signaled. Reset must be manual) and cannot change behavior case by case.
  4. Forgot signal & slots: their purpose is not "weak up a waiting thread when something happens". They are just a completely different (an unrelated) concept.
  5. CriticalSection is the "single process" version for the more general Mutex (that is the same for windows and POSIX, with the difference that Windows does not distinguish between recursive and non-recursive: all are recursive).

That said, behind events (and condition variable) and mutex (and critical sections) there is a different scope of work:

Mutexes -essentially- protect code that must not be run concurrently. They essentially means "if this piece of code is already in execution wait until the other will complete it".

Events -essentially- protect the access to resource not yet been produced. It essentially means "wait here until someone can grant that it is possible to go over".

The difference is that mutexes are signaled by the same who has unsignaled them, while events are unsignaled by the one that had been waked-up from who signaled them. Note the the term "signal", here, has noting to do with the notion of signal in "boost signal", that is much more related to the concept of "delegate chain". But it is all another story.

Upvotes: 1

Related Questions