Threads Lifetime
The parent has to take care of their child. This simple idea has big consequences for a thread lifetime. The following program starts a thread that displays its ID.
// threadWithoutJoin.cpp
#include <iostream> #include <thread> int main(){ std::thread t([]{std::cout << std::this_thread::get_id() << std::endl;}); }
But the program run results in an unexpected result.
What’s the reason?
join and detach
The lifetime of the created thread t ends with its callable unit. The creator has two choices. First: it waits until its child is done (t.join()). Second: it detaches itself from its child: t.detach(). A thread t with the callable unit (you can create threads without a callable unit) is joinable, in case there were no t.join() or t.detach calls to the thread. A joinable thread destructor throws std::terminate exception. Thus, the program terminates. That is the reason, the actual run terminated unexpectedly.
The solution to this problem is simple. By calling t.join(), the program behaves as it should.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
// threadWithJoin.cpp
#include <iostream> #include <thread> int main(){ std::thread t([]{std::cout << std::this_thread::get_id() << std::endl;}); t.join(); }
A short side note: The challenges of detaching
Of course, you can use t.detach() instead of t.join() in the program above. The thread t is not joinable anymore, and its destructor didn’t call std::terminate. Seems terrible because now the program behavior is undefined because the lifetime of the object std::cout is not ensured. The execution of the program goes a little bit odd.
I will elaborate more on this issue in the following article.
Moving threads
Until now, it was quite easy. But that has not to be forever.
It is impossible to copy a thread (copy semantic); you can only move (move semantic) it. If a thread is moved, it’s a lot more challenging to deal with its lifetime in the right way.
// threadMoved.cpp
#include <iostream> #include <thread> #include <utility> int main(){ std::thread t([]{std::cout << std::this_thread::get_id();}); std::thread t2([]{std::cout << std::this_thread::get_id();}); t= std::move(t2); t.join(); t2.join(); }
Both threads – t1 and t2 should do a simple job: print their IDs. In addition, thread t2 will be moved to t: t= std::move(t2). In the end, the main thread takes care of its children and joins them. But wait. That’s far away from my expectations:
What is going wrong? We have two issues:
- By moving (taking ownership of) the thread t2, t gets a new callable unit, and its destructor will be called. So it’s destructor calls std::terminate, because it is still joinable.
- Thread t2 has no associated callable unit. The invocation of join on a thread without a callable unit leads to the exception std::system_error.
I fixed both errors.
// threadMovedFixed.cpp
#include <iostream> #include <thread> #include <utility> int main(){ std::thread t([]{std::cout << std::this_thread::get_id() << std::endl;}); std::thread t2([]{std::cout << std::this_thread::get_id() << std::endl;}); t.join(); t= std::move(t2); t.join(); std::cout << "\n"; std::cout << std::boolalpha << "t2.joinable(): " << t2.joinable() << std::endl; }
As a result, thread t2 is not joinable anymore.
scoped_thread
If it’s too bothersome for you to take care of the lifetime of your threads by hand, you can encapsulate a std::thread in your wrapper class. This class should automatically call join in his destructor. Of course, you can go the other way around and call detach. But you know, there are a few issues with detachment.
Anthony Williams created such a valuable class. He called it scoped_thread. The constructor checks that the thread is joinable and joins it finally in the destructor. Because the copy constructor and copy assignment operator is declared delete, objects of scoped_thread can not be copied to or assigned from.
// scoped_thread.cpp
#include <iostream> #include <thread> #include <utility> class scoped_thread{ std::thread t; public: explicit scoped_thread(std::thread t_): t(std::move(t_)){ if ( !t.joinable()) throw std::logic_error("No thread"); } ~scoped_thread(){ t.join(); } scoped_thread(scoped_thread&)= delete; scoped_thread& operator=(scoped_thread const &)= delete; }; int main(){ scoped_thread t(std::thread([]{std::cout << std::this_thread::get_id() << std::endl;})); }
What’s next?
In the next post, I will deal with passing data to threads. (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, 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, Stephen Kelley, Kyle Dean, Tusar Palauri, 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, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
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 |
Seminars
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
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++
- C++20
Online Seminars (German)
- Embedded
Programmierung mit modernem C++ (24. Sep. 2024 bis 26.
Sep. 2024)
Contact Me
- Mobil: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!