Managing a Thread

Detaching a Thread

  • Instead of calling join(), we can call detach()

    • The parent thread will continue executing
    • The child thread will run until it completes
    • Or the program terminates
    • Analogous to a "daemon" process
  • When an execution thread is detached

    • The std::thread object is no longer associated with it
    • The destructor will not call std::terminate()

Check out Anthony Williams book C++ concurrency in action

Exception in Parent Thread

  • The destructors are called for every object in scope

    • Including std::thread's destructor
    • This checks whether join() or detach() have been called
    • If neither, it calls std::terminate()
  • We must call either join() or detach() before the thread is destroyed

    • In all paths through the code

Try/catch Solution

  • The obvious solution is to add a try/catch block. This is verbose and not very elegant.

#include <exception>
#include <iostream>
#include <thread>

void hello() { std::cout << "Hello, Thread!\n"; }

int main() {
  std::thread thr(hello);

  try {
    // throw std::exception();

    thr.join();

  } catch (const std::exception &e) {
    std::cout << "exception: " << e.what() << "\n";
    thr.join();
  }
} // calls the destructor

RAII Solution

  • A better solution is to use the RAII idiom

    • Wrap the std::thread inside a class
    • The class's destructor calls join() on the std::thread object
  • An std::thread object can only be joined once

  • The joinable() member function

    • Returns false if join() or detach() have already been called, or if the thread object is not associated with an execution thread.
    • Returns true if we need to call join()

#include <exception>
#include <iostream>
#include <thread>
#include <utility>

class thread_guard {
  std::thread thr;

public:
  explicit thread_guard(std::thread &&thr) : thr(std::move(thr)) {}

  ~thread_guard() {
    if (thr.joinable()) {
      thr.join();
    }
  }

  // prevent copies
  thread_guard(const thread_guard &) = delete;
  thread_guard &operator=(const thread_guard &) = delete;
};

void hello() { std::cout << "Hello, thread!\n"; }

int main() {
  try {
    std::thread thr(hello);
    thread_guard tguard(std::move(thr));

    // throw std::exception();
  } catch (const std::exception &e) {
    std::cout << "exception caught: " << e.what() << "\n";
  }
}

  • The destructors are called in reverse order

    • The thread_guard's destructor is called first
    • If necessary, it calls thr.join() and waits for the execution thread to finish
    • The thread_guard's std::thread member is then destroyed
    • It is not associated with an execution thread
    • Its destructor does not call std::terminate()
  • This applies in normal execution, and when an exception is thrown

Stopping Threads

  • Execution threads can be interrupted or stopped
    • Killed, cancelled, terminated
  • In general, abruptly terminating a thread is not a good idea
  • std::thread does not support this
    • The Williams book has an interruptible_thread class
    • The operating system can be used to stop the underlying execution thread

C++20 and std::jthread

  • Destructor does not call std::terminate()
    • Calls join() if necessary
  • Supports cooperative interruption