I continue my journey with concurrency patterns in today’s post. The Thread-Safe Interface fits very well when the critical sections are just objects.
The naive idea to protect all member functions of a class with a lock causes, in the best case, a performance issue and, in the worst case, a deadlock.
The small code snippet has a deadlock.
crit.memberFunction1 causes the mutex
mut to be locked twice. For simplicity reasons, the lock is a scoped lock. Here are the two issues:
lockis a recursive lock, the second
lockis a non-recursive lock, the second
memberFunction2leads to undefined behavior. Most of the time, you get a deadlock.
The thread-safe interface overcomes both issues.
The Thread-Safe Interface
Here is the straightforward idea of the Thread-Safe Interface.
- All interface member functions (
public) use a lock.
- All implementation member functions (
private) must not use a lock.
- The interface member functions call only protected or private member functions but no public member functions.
The following program shows the usage of the Thread-Safe Interface.
Three threads, including the main thread, use instances of
Critical. Thanks to the Thread-Safe Interface, all calls to the public API are synchronized. The mutex
mut in line (1) is mutable and can be used in the constant member function
The advantages of the thread-safe interface are threefold.
- A recursive call of a mutex is not possible. Recursive calls on a non-recursive mutex are undefined behavior in C++ and usually end in a deadlock.
- The program uses minimal locking and, therefore, minimal synchronization. Using just a
std::recursive_mutexin each member function of the class
Criticalwould end in more expensive synchronization.
- From the user’s perspective,
Criticalis straightforward to use because synchronization is only an implementation detail.
Each interface member function delegates its work to the corresponding implementation member function. The indirection overhead is a typical disadvantage of the Thread-Safe Interface.
The output of the program shows the interleaving of the three threads.
Although the Thread-Safe Interface seems easy to implement, there are two grave perils you have to keep in mind.
Using a static member in your class or having virtual interfaces requires special care.
When your class has a static member that is not constant, you must synchronize all member function calls on the class instances.
Now, the class
Critical has a static member
called (line 32) to count how often the implementation functions were called. All instances of
Critical use the same static member and have, therefore, to be synchronized. Since C++17, static data members can be declared inline. An inline static data member can be defined and initialized in the class definition.
When you override a virtual interface function, the overriding function should have a lock even if the function is private.
In the calls,
base2.interface the static type of
Base, and, therefore, the
interface is accessible. Because the interface member function is virtual, the call happens at run time using the dynamic type
Derived. At last, the private member function
interface of the class
Derived is invoked.
The program’s output shows the unsynchronized invocation of Derived’s interface function.
There are two typical ways to overcome this issue.
- Make the member function interface a non-virtual member function. This technique is called NVI (Non-Virtual Interface). The non-virtual member function guarantees that the interface function of the base class
Baseis used. Additionally, overriding the interface function using
overridecauses a compile-time error because there is nothing to override.
- Declare the member function interface as
virtual void interface() final. Thanks to
final, overriding an as
finaldeclared virtual member function causes a compile-time error.
Although I presented two ways to overcome the challenges of virtuality, I strongly suggest using the NVI idiom. Use early binding if you don’t need late binding (virtuality). You can read more about NVI in my post:The Template Method.
Guarded Suspension applies a different strategy to deal with mutation. It signals when it is done with its mutation. In my next post, I will write about Guarded Suspension.
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,