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()
- Including
-
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::threadinside a class - The class's destructor calls join() on the
std::threadobject
- Wrap the
-
An
std::threadobject 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::threadmember 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::threaddoes 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