It is common that programmers use Auto Reset and Manual Reset Events in a multithreaded scenario for asynchronous notifications. However, most people are naïve in choosing the right type of Event. Many websites have dealt with describing Event objects but to an extent that stops at their definition. This article goes a step deeper to explore and find out how and when to use them and what they have for us.
This article assumes that the reader is aware of Kernel objects and synchronization primitives. This article does not deal with any synchronization primitives other than Event Objects. This article addresses Event Objects that are part of the Windows OS.
Types of Event Objects
Windows offers two kinds of Events:
- Auto Reset Event, once set, gets reset to the non-signaled state after at least one thread that is waiting on it is released.
- On the other hand, once a Manual Reset Event is set, it requires an explicit reset to go to the non-signaled state.
It is important to keep in mind that events do not follow the concept of ownership, i.e., consecutive wait calls on the same event object on the same thread will be blocking calls unlike a Mutex, Critical Section, or a Semaphore. Also, any thread can signal an event object just as any thread can wait for an event object to be signaled.
The following sections in the article illustrate the differences between these two events and which one to use when.
Auto Reset Event
To explain it better with an example, let us say there are three threads, A, B, and C, waiting on an auto reset event AE. When AE is Signaled (Set), the OS scheduler performs the following three operations atomically:
- Signaling of the event.
- Releasing a waiting thread - Only one of the threads (A, B, or C) gets released from the WAIT state. The other threads continue to be in the WAIT state waiting for this event to be signaled.
- Resetting the event.
The threads which are not released are not aware that the event got signaled and reset. It is not under the control of the programmer to choose the thread to be released from the WAIT state. It is completely at the discretion of the OS scheduler. Because of this non-deterministic nature, Auto Reset Events are typically used for synchronization among threads where one thread waits for the event to be signaled and the other thread(s) signal the event. The waiting thread that is released, after completing its task, usually goes back to the WAIT state (to get notified by the same Auto Reset Event).
A very common use for Auto-Reset Events is a task dispatcher. A task dispatcher maintains a queue of tasks to be dispatched. The task-queue is examined and dispatched (one at a time) by a dispatcher thread. Once the queue becomes empty, the dispatcher thread goes to the WAIT state waiting for an auto-reset event to be signaled. This auto-reset event is signaled every time an item is added to the queue. Thus, when a task gets queued, the dispatcher thread wakes up, dispatches the task (until the queue gets empty), and goes back to the WAIT state.
Notice that there is only one waiting entity and one or more signaling entities. In the above example, the Task Dispatcher Thread is the waiting entity, and other threads that add a task to the queue are the signaling entities.
Another good example of an Auto Reset Event is it can be used to implement a thread pool mechanism where a pool of threads can wait on a single Auto Reset Event (unlike the typical use where only one thread waits). When a work item is queued, the Auto Reset Event is set, which releases only one waiting thread among the pool of threads, which in turn dequeues the work item and executes it.
Manual Reset Event
Upon signaling a Manual Reset Event, all the threads that wait on this event get released from the WAIT state.
For example, let us say there are three threads, A, B, and C, waiting on a Manual Reset Event, MRE. When MRE is signaled (Set), the OS scheduler performs the following operations atomically:
- Signaling of the event.
- Releasing the threads - All threads that are currently waiting on this ME are released.
Note the difference in sequence from that of the Auto Reset Event. The third step, which is the reset of the event, is left to the programmer. The event stays signaled until the programmer resets it explicitly. Any future waits on this event will immediately return until the event is reset.
Manual Reset Events are good for cases where multiple threads need to be notified of a single event.
A typical use case of a Manual-Reset Event is shutdown synchronization. Suppose you have ‘n’ worker threads and you want to make sure you have a clean shutdown process, you can make all the ‘n’ threads wait on a common Manual Reset Event object. When the shutdown process is to be initiated, it will be sufficient to signal the Manual Reset Event and wait for the ‘n’ threads to exit and then reset the event. But, there is a caveat to waiting for the threads to exit. It is possible that some thread may never return (perhaps stuck in deadlock). One approach would be to wait for the threads to exit for a specified period of time and terminate them if they do not exit. Those are all issues that have to be factored in the design.
When to Call Reset?
The answer to this is a choice by design that has to be made knowing the consequences of the placement of the Reset call. A wrong choice could lead to disaster.
Externally Triggered Reset
- All the waitable threads are in the WAIT state when you signal the event. This need not be true since some of the threads might have exited the previous wait and executing some code before going back to the WAIT state. In such cases, these threads will miss the occurrence of this event if the Reset call happens before these threads go back to the wait state. This introduces unpredictability in the system.
- Any new thread(s) that get created in the time window between the set and the (immediate) reset call will not wait since the event is in the signaled state. This might be a design choice, but nevertheless, this case should be evaluated.
This is a scenario where the Manual Reset Event behaves like a switch which is externally controlled. For instance, one could use a Manual Reset Event to implement a Pause/Resume mechanism for a group of tasks each of which has a corresponding thread to perform. Again, it has to be considered if it is necessary to make sure all the activities have ceased to happen after the reset call.
Not Calling Reset
This can be used if it is known that a particular event occurs only once or the next occurrence does not matter to the system. This can be applied to the Shutdown Processing example cited earlier in this article.
Reset Based on Other Complex Logic
If the Reset is based on some other complex logic, the advice is to look for alternative synchronization primitives since it is very difficult to avoid race conditions that could occur with Manual Reset Events.
Event objects are useful in resolving a variety of synchronization problems in a simple way. But it is very important to be aware of making a wrong choice of Event for a given situation. Unfortunately, most programmers are carried away with the explicit reset control of a Manual Reset Event, which in most cases does not suit their scenario. This leads to race conditions and other unpredictable behavior in their software. I hope this article would help programmers understand and choose the type of Event object befitting their scenario.
- 17 Aug 2009 – Initial draft.