Threads Lifetime

Contents[Show]

The parent has to take care of its 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 callable unit (you can create threads without 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 for 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 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)

 

{t

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

 

 

 

 

 

 

 

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
0 #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

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 168

All 277622

Currently are 142 guests and no members online