The monitor object design pattern synchronizes concurrent member function execution to ensure that only one member function at a time runs within an object. It also allows object’s member functions to schedule their execution sequences cooperatively. (Pattern-Oriented Software Architecture: Patterns for Concurrent and Networked Objects)
The Monitor Object design pattern synchronizes concurrent member function execution to ensure that only one member function runs within an object at a time. It also allows an object’s member functions to schedule their execution sequences cooperatively.
Also know as
- Thread-Safe Passive Object
If many threads access a shared object concurrently, the following challenges exist.
- Due to the concurrent access, the shared object must be protected from non-synchronized read and write operations to avoid data races.
- The necessary synchronization should be part of the implementation, not the interface.
- When a thread is done with the shared object, a notification should be triggered so the next thread can use the shared object. This mechanism helps avoid and improves the system’s overall performance.
- After the execution of a member function, the invariants of the shared object must hold.
A client (thread) can access the Monitor Object’s synchronized member functions, and due to the monitor lock, only one synchronized member function can run at any given time. Each Monitor Object has a monitor condition that notifies the waiting clients.
- Monitor Object: The Monitor Object supports one or more member functions. Each client must access the object through these member functions, and each member function runs in the client’s thread.
- Synchronized member functions: The Monitor Object supports the synchronized member functions. Only one member function can execute at any given point in time. The Thread-Safe Interface helps to distinguish between the interface member functions (synchronized member functions) and the implementation member functions of the Monitor Object.
- Monitor lock: Each Monitor Object has one monitor lock, which ensures that at most one client can access the Monitor Object at any given time.
- Monitor condition: The monitor condition allows separate threads to schedule their member function invocations on the Monitor Object. When the current client is done with its invocation of the synchronized member functions, the next waiting client is awakened to invoke the Monitor Object’s synchronized member functions.
While the monitor lock ensures the synchronized member functions’ exclusive access, the monitor condition guarantees minimal waiting for the clients. Essentially, the monitor lock protects from data races and the condition monitor from deadlocks.
The interaction between the Monitor Object and its components has different phases.
- When a client invokes a synchronized member function on a Monitor Object, it must first lock the global monitor lock. If the client successfully locks, it executes the synchronized member function and unlocks the monitor lock. If the client is not successful, the client is blocked.
- When the client is blocked because it cannot progress, it waits until the monitor condition sends a notification. This notification happens when the monitor is unlocked. The notification can be sent to one or all the waiting clients. Typically, waiting means resource-friendly sleeping in contrast to busy waiting.
- When a client gets the notification to resume, it locks the monitor lock and executes the synchronized member function. The monitor lock is unlocked at the end of the synchronized member function. The monitor sends a notification to signal that the next client can execute its synchronized member function.
Pros and Cons
What are the advantages and disadvantages of the Monitor Object?
- The client is not aware of the implicit synchronization of the Monitor Object, and the synchronization is fully encapsulated in the implementation.
- The invoked synchronized member functions will eventually be automatically scheduled. The notification/waiting mechanism of the monitor condition behaves as a simple scheduler.
- It is often quite challenging to change the synchronization mechanism of the synchronization member functions because the functionality and the synchronization are strongly coupled.
- When a synchronized member function invokes directly or indirectly the same Monitor Object, a deadlock may occur.
The following example defines a
The key idea of the example is that the Monitor Object is encapsulated in a class and can, therefore, be reused. The class
Monitor uses a
std::mutex as monitor lock and
std::condition_variable as monitor condition. The class
Monitor provides the minimal interface that a Monitor Object should support.
ThreadSafeQueue in line (1) extends
std::queue in line (3) with a thread-safe interface.
ThreadSafeQueue derives from the class
Monitor and uses its member functions to support the synchronized member functions
add and get. The member functions
get use the monitor lock to protect the Monitor Object, particularly the non-thread-safe
add notifies the waiting thread when a new item was added to
myQueue. This notification is thread-safe. The member function
get (line (3)) deserves more attention. First, the
wait member function of the underlying condition variable is called. This
wait call needs an additional predicate to protect against spurious and lost wakeups (C++ Core Guidelines: Be Aware of the Traps of Condition Variables). The operations modifying
myQueue (lines 4 and 5) must also be protected because they can interleave with the call
myQueue.push(val) (line 6). The Monitor Object
safeQueue line (7) uses the lambda functions in lines (8) and (9) to add or remove a number from the synchronized
ThreadSafeQueue itself is a class template and can hold values from an arbitrary type. One hundred clients add 100 random numbers between 1 – 6 to
safeQueue (line 7), while hundred clients remove these 100 numbers concurrently from the
safeQueue. The output of the program shows the numbers and the thread ids.
With C++20, the program
monitorObject.cpp can be further improved. First, I include the header
<concepts> and use the concept
std::predicate as a restricted type parameter in the function template
wait (line 10). The concept
std::predicate ensures that the function template
wait can only be instantiated with a predicate. Predicates are callables that return a boolean as a result.
Second, I use
std::jthread instead of
std::jthread s an improved
std::thread in C++20 that automatically joins in its destructor if necessary.
The Active Object and the Monitor Object are similar but distinct in a few important points. Both architectural patterns synchronize access to a shared object. The member functions of an Active Object are executed in a different thread, but the Monitor Object member functions in the same thread. The Active Object decouples its member function invocation better from its member function execution and is, therefore, easier to maintain.
DONE! I have written around 50 posts about patterns. In my next posts, I will write about unknown features in C++17, dive deeper into C++20, and present the upcoming new C++23 standard. I will start this journey with C++23.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, and Marco Parri Empoli.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,