C++11 Concurrency - Part 1 : Start Threads
C++11 introduced a new thread library. This library includes utilities for starting and managing threads. It also contains utilities for synchronization like mutexes and other locks, atomic variables and other utilities. In this serie of posts, I will try to explain most of the features provided by this new library.
To compile the samples of this article, you will need a compiler with C++11 support. In my case, I used GCC 4.6.1 (you need to pass the "-std=c++0x" or "-std=c++11" option to get the C++11 support activated).
Starting threads
Starting a new thread is very easy. When you create an instance of a std::thread, it will automatically be started. When you create a thread you have to give it the code it will execute. The first choice for that, is to pass it a function pointer. Let's start with the very common Hello World:
#include <thread> #include <iostream> void hello(){ std::cout << "Hello from thread " << std::endl; } int main(){ std::thread t1(hello); t1.join(); return 0; }
All the threads utilities are located in the thread header. An interesting thing in this first example is the call to the join() function. Calling this function forces the current thread to wait for the other one (in this case, the main thread has to wait for the thread t1 to finish). If you omit this call, the result is undefined. The program can print Hello from thread and a new line, can print just Hello from thread without new line or can print nothing. That's because the main thread can return from the main function before the t1 thread finishes its execution.
Distinguishing threads
Each thread has a single id allowing us to distinguish each of them. The std::thread class has a get_id() function returning an unique id for this thread. You can manipulate the current thread with the functions from the std::this_thread namespace. The next example starts with threads and each of them prints its id:
#include <thread> #include <iostream> #include <vector> void hello(){ std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; } int main(){ std::vector<std::thread> threads; for(int i = 0; i < 5; ++i){ threads.push_back(std::thread(hello)); } for(auto& thread : threads){ thread.join(); } return 0; }
Starting each thread one after one and then storing them into a vector is a common way to handle several threads. With that, you can easily change the number of threads. Even with a very little sample like this one, the results is not predictable. The theoretic case:
Hello from thread 140276650997504 Hello from thread 140276667782912 Hello from thread 140276659390208 Hello from thread 140276642604800 Hello from thread 140276676175616
Is, in my case, also the least common. You can also get results like this one:
Hello from thread Hello from thread Hello from thread 139810974787328Hello from thread 139810983180032Hello from thread 139810966394624 139810991572736 139810958001920
Or a lot of another results. This is because of interleaving. You have no way to control the order of execution of threads. A thread can be preempted at any moment and the appends to the out stream are made one after one (first the append of the string, then append the id and finally the new line), so a thread can print its first part and then be interrupted to print its second part after all the others threads.
Start a thread with a lambda
When the code that has to be executed by each thread is very small, you don't necessary want to create a function for that. In that case, you can use a lambda to define the executed by a thread. We can rewrite the code of the last sample using lambda easily:
#include <thread> #include <iostream> #include <vector> int main(){ std::vector<std::thread> threads; for(int i = 0; i < 5; ++i){ threads.push_back(std::thread([](){ std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl; })); } for(auto& thread : threads){ thread.join(); } return 0; }
Here we just used a lambda expression instead of the function pointer. Of course, this produces the exact same result as the previous sample.
Next
In the next post of this series, we will see how to protect code using locks.
The source code for each sample is available on Github.
Comments
Comments powered by Disqus