If the previous post showed something, it’s that you should use mutexes with great care. That’s why you should wrap them in a lock.
Locks take care of their resource following the RAII idiom. A lock automatically binds its mutex in the constructor and releases it in the destructor. This considerably reduces the risk of a deadlock because the runtime takes care of the mutex.
Locks are available in two flavors in C++11. std::lock_guard for the simple, and std::unique-lock for the advanced use case.
First is the simple use case.
With so little code, mutex m ensures access to the critical section sharedVariable= getVar() is sequential. Sequential means – in this particular case – that each thread gains access to the critical section in order. The code is simple but prone to deadlocks. Deadlock appears if the critical section throws an exception or the programmer forgets to unlock the mutex. With std::lock_guard we can do this more elegant:
That was easy. But what about the opening and closing brackets? The lifetime of std::lock_guard is limited by the brackets (http://en.cppreference.com/w/cpp/language/scope#Block_scope). That means its lifetime ends when it leaves the critical section. At that time, the destructor of std::lock_guard is called, and – I guess you know it – the mutex is released. It happens automatically, and, in addition, it happens if getVar() in sharedVariable = getVar() throws an exception. Of course, function body scope or loop scope also limits the lifetime of an object.
std::unique_lock is mightier but more expansive than its small brother std::lock_guard.
A std::unique_lock enables youin addition to std::lock_guard
- create it without an associated mutex
- create it without a locked associated mutex
- explicitly and repeatedly set or release the lock of the associated mutex
- move the mutex
- try to lock the mutex
- delayed lock the associated mutex
But why is it necessary? Remember the deadlock from the post Risks of mutexes? The reason for the deadlock was the mutexes were locked in a different sequence.
The solution is easy. The function deadlock has to lock their mutex in an atomic fashion. That’s precisely what happens in the following example.
If you call the constructor of std::unique_lock with the argument std::defer_lock, the lock will not be locked automatically. It happens in lines 14 and 19. The lock operation is performed atomically in line 23 using the variadic template std::lock. A variadic template is a template that can accept an arbitrary number of arguments. Here, the arguments are locks. std::lock tries to get all locks in an atomic step. So, he fails or gets all of them.
In this example, std::unique_lock takes care of the lifetime of the resources, std::lock locks the associated mutex. But you can do it the other way around. In the first step, you lock the mutexes; in the second std::unique_lock takes care of the lifetime of resources. Here is a sketch of the second approach.
Now, all is fine. The program runs without deadlock.
A side note: Special deadlocks
It’s an illusion that only a mutex can produce a deadlock. Each time a thread has to wait for a resource, a deadlock lurks near while it is holding a resource.
Even a thread is a resource.
The program immediately stands still.
What’s happening? The lock of output stream std::cout and the waiting of the main thread for its child t cause the deadlock. By observing the output, you can easily see in which order the statements will be performed.
In the first step, the main thread executes lines 19 – 21. It waits in line 21 using the call t.join() until its child t is done with its work package. The main thread is waiting while it locks the output stream. But that’s precisely the resource the child is waiting for. Two ways to solve this deadlock come to mind.
- The main thread locks the output stream std::cout after the call t.join().
- The main thread releases its lock by an additional scope. This is done before the t.join() call.
In the next post, I’ll talk about reader-writer locks. Reader-writer locks empower you since C++14 to distinguish between reading and writing threads. So, the contention on the shared variable will be mitigated because an arbitrary number of reading threads can access the shared variable simultaneously. (Proofreader Alexey Elymanov)
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, Marco Parri Empoli, moon, and Philipp Lenk.
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|
|My special thanks to SHAVEDYAKS|
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,