Threads Lifetime

Contents[Show]

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.

 threadForgetJoin 

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.

// threadWithJoin.cpp

#include <iostream>
#include <thread> int main(){ std::thread t([]{std::cout << std::this_thread::get_id() << std::endl;}); t.join(); }

 

threadJoin

A short side note: The challenges of detach

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 bad, because now the program behaviour is undefined, because the lifetime of the object std::cout is not ensured. The execution of the program goes a little bit odd.

 

threadDetach

I will elaborate more on this issue in the next article.

Moving threads 

Until now, it was quite easy. But that has not to be forever.

It is not possible to copy a thread (copy semantic), you can only move (move semantic) it. In case a thread will be moved, it's a lot more difficult 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 to that, Thread t2 will be moved to t: t= std::move(t2). At the end, the main thread takes care of its children and joins them. But wait. That's far away from my expectations:

 threadMove

What is going wrong? We have two issues:

  1. By moving (taking ownership of)  the thread t2, t gets a new callable unit and its destructor will be called. So t's destructor calls std::terminate, because it is still joinable.
  2. Thread t2 has no associated callable unit. The invocation of join on a thread without 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 any more.

threadMoveRight

scoped_thread

In case 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 own 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 detach.

Anthony Williams created such a valuable class. He called it scoped_thread. In the constructor it checks that the thread is joinable and joins it finally in the destructor. Because the copy constructor and copy assignment operator are declared as 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 deal with passing data to threads. (Proofreader Alexey Elymanov)

 

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Dimitrov Tsvetomir, Leo Goodstadt, Eduardo Velasquez, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, and Robin Furness.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, and Said Mert Turkal.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

 

Comments   

0 #21 APK Download 2016-11-28 12:39
What's up, this weekend is fastidious designed for me, because this point in time i am reading this fantastic educational paragraph here at my house.
Quote
0 #22 Huug Schenk 2017-03-22 16:41
Is the call to t2.joinable() after doing an std::move(t2) safe? I thought the only thing that is guaranteed about t2 after the move is that it can be assigned to.
Quote
+1 #23 Dario Entertainer 2017-04-13 22:27
Just desire to say your article is as amazing. The clearness in your submit is simply spectacular
and i could assume you are knowledgeable in this subject.

Fine with your permission allow me to snatch your RSS feed to stay up to date with impending post.

Thanks a million and please carry on the enjoyable work.
Quote
0 #24 Marianne 2017-09-02 04:48
Hey very interesting blog!
Quote

My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 370

Yesterday 5761

Week 370

Month 125776

All 7188066

Currently are 333 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments