threadForgetJoin

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.

 threadForgetJoin 

What’s the reason?

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

Be part of my mentoring programs:

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (starts March 2024)
  • Do you want to stay informed: Subscribe.

     

    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 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.

     

    threadDetach

    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:

     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 it’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 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.

    threadMoveRight

    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, 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, 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, Philipp Lenk, Hobsbawm, and Charles-Jianye Chen.

    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)

    Contact Me

    Modernes C++ Mentoring,

     

     

    0 replies

    Leave a Reply

    Want to join the discussion?
    Feel free to contribute!

    Leave a Reply

    Your email address will not be published. Required fields are marked *