C++11 provides richer support for concurrency than the previous standard. Among the new features is the std::thread class (from the <thread> header) that represents a single thread of execution. Unlike other APIs for creating threads, such as CreateThread, std::thread can work with (regular) functions, lambdas or functors (i.e. classes implementing operator()) and allows you to pass any number of parameters to the thread function.
Let’s see a simple example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <thread> void func() { // do some work } int main() { std::thread t(func); t.join(); return 0; } |
In this example t is a thread object representing the thread under which function func() runs. The call to join blocks the calling thread (in this case the main thread) until the joined thread finishes execution.
If the thread function returns a value, it is ignored. However, the function can take any number of parameters.
1 2 3 4 5 6 7 8 9 10 11 12 |
void func(int a, double b, const std::string& c) { std::cout << a << ", " << b << ", " << c.c_str() << std::endl; } int main() { std::thread t(func, 1, 3.14, "pi"); t.join(); return 0; } |
The output is:
1 |
1, 3.14, pi |
It is important to note that the parameters to the thread function are passed by value. If you need to pass references you need to use std::ref or std::cref.
The following program prints 42.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void func(int& a) { a++; } int main() { int a = 42; std::thread t(func, a); t.join(); std::cout << a << std::endl; return 0; } |
But if we change to t(func, std::ref(a)) it prints 43.
In the next example we execute a lambda on a second thread. The lambda doesn’t do much, except for printing some message and sleeping for a while.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <thread> #include <chrono> #include <iostream> auto func = []() { std::cout << "thread " << std::this_thread::get_id() << " started" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(rand()%10)); std::cout << "thread " << std::this_thread::get_id() << " finished" << std::endl; }; int main() { srand((unsigned int)time(0)); std::thread t(func); t.join(); return 0; } |
The output looks like this (obviously the id of the threads differ for each run).
1 2 |
thread 5412 started thread 5412 finished |
But if we start two threads, the the output looks different:
1 2 3 4 5 |
std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); |
1 2 3 4 |
thread thread 10180 started9908 started thread 10180 finished thread 9908 finished |
The reason is the function running on a separate thread is using std::cout, which is an object representing a stream. This is a shared object of a class that is not thread-safe, therefore the access from different threads to it must be synchronized. There are different mechanisms provided by C++11 to do that (will discuss them in a later post), but one of them is std::mutex (notice there are four flavors of mutexes).
The correct, synchronized code should look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <mutex> std::mutex m; auto func = []() { m.lock(); std::cout << "thread " << std::this_thread::get_id() << " started" << std::endl; m.unlock(); std::this_thread::sleep_for(std::chrono::seconds(rand()%10)); m.lock(); std::cout << "thread " << std::this_thread::get_id() << " finished" << std::endl; m.unlock(); }; |
1 2 3 4 |
thread 5032 started thread 7672 started thread 5032 finished thread 7672 finished |
In this examples I have used several functions from the std::this_thread namespace (also defined in the <thread> header). The helper functions from this namespace are:
- get_id: returns the id of the current thread
- yield: tells the scheduler to run other threads and can be used when you are in a busy waiting state
- sleep_for: blocks the execution of the current thread for at least the specified period
- sleep_util: blocks the execution of the current thread until the specified moment of time has been reached
Apart from join() the thread class provides two more operations:
- swap: exchanges the underlying handles of two thread objects
- detach: allows a thread of execution to continue independently of the thread object. Detached threads are no longer joinable (you cannot wait for them).
1 2 3 4 5 6 7 |
int main() { std::thread t(funct); t.detach(); return 0; } |
What happens though if a function running on a separate thread throws an exception? You cannot actually catch that exception in the thread that’s waiting for the faulty thread, because std::terminate is called and this aborts the program.
If func() throws an exception, the following catch block won’t be reached.
1 2 3 4 5 6 7 8 9 10 11 12 |
try { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); } catch(const std::exception& ex) { std::cout << ex.what() << std::endl; } |
What can be done then? To propagate exceptions between threads you could catch them in thread function and store them in a place where they can be lately looked-up. Possible solutions are detailed here and here.
In future posts we will look at other concurrency features from C++11 (such as synchronization mechanisms or tasks).
Pingback: Introduction to C++11 Concurrency | musingstudio