The standard library that ships with the new C++11 contains a set of classes to use threads. Before this, we needed to use the OS specific thread facilities each OS provides making our programs hard to port to other platforms.
Anyway, as today (November 16th, 2012), I tried threads using g++ 4.7 in Linux, Windows (through mingw), Mac and NetBSD and I just had success in Linux, Windows and Mac do not implement the thread features and NetBSD misses some details on the implementation (the this_thread::sleep_for()
method, for example). Microsoft Visual Studio 2012 ships with good thread support.
To define a thread, we need to use the template class std::thread
and we need to pass it a function pointer, a lambda expression or a functor. Look at this example:
#include <cstdio> #include <thread> using namespace std; void run() { for (auto i = 0; i < 5; i++) { printf("%d\n", i); } } int main() { thread t1(run); thread t2(run); thread t3(run); t1.join(); t2.join(); t3.join(); return 0; }
In my example you can see several things:
* You need to #include <thread>
to support threads.
* The thread constructor starts the thread immediately.
* The t1.join()
call stops “this thread” until the thread “t1” terminates.
The output is something similar to this:
0
1
2
3
4
0
1
2
3
4
0
1
2
3
4
The execution is too short that all the code associated to a thread terminates before a thread switch occurs.
If we add a call to the method this_thread::yield()
in the thread function:
void run() { for (auto i = 0; i < 5; i++) { printf("%d\n", i); this_thread::yield(); } }
The output will be something like this:
0
1
2
3
0
0
4
1
1
2
2
3
3
4
4
The function this_thread::yield()
suggests the OS to reschedule the execution of this thread, letting other threads to be executed instead of this. Anyway, yield
only suggests and the OS scheduler will decide if perform a thread switch or not.
Let’s replace this_thread::yield()
to this_thread::sleep_for()
in our example and let’s see what happens:
void run() { for (auto i = 0; i < 5; i++) { printf("%d\n", i); this_thread::sleep_for(chrono::milliseconds(5)); } }
To use the time facilities, you must #include <chrono>
.
this_thread::sleep_for()
sleeps this thread for the time passed as parameter. When this function executes, the scheduler switch to other thread to be executed by the processor; that is why the output is similar to this one:
0
0
0
1
1
1
2
2
2
3
3
3
4
4
4
If I could pass parameters to my thread function, I can pass them through the thread constructor. The thread constructor performs a binding on the thread function, as shown in this code:
#include <cstdio> #include <thread> using namespace std; void run(int n) { for (auto i = 0; i < 5; i++) { printf("%d: %d\n", n, i); this_thread::sleep_for(chrono::milliseconds(5)); } } int main() { thread t1(run, 1); thread t2(run, 2); thread t3(run, 3); t1.join(); t2.join(); t3.join(); return 0; }
As you can see, the run()
method receives an parameter that is passed through the thread constructor.
The question is: What if I use cout
instead of printf
in my example?
void run(int n) { for (auto i = 0; i < 5; i++) { cout << n << ": " << i << endl; } }
The output will be similar to this one:
1: 0
1: 1
1: 2
1: 3
1: 4
2: 0
2: 1
2: 2
23: 0
3: 1
3: 2
: 3: 3
3: 4
3
2: 4
The C++ streams are not guaranteed to be thread safe (actually, I got such result using g++ 4.7 in Linux), so, the weird output occurs because a thread switch occurred before one line has been completed.
When using threads, you can deal with shared resources (in my example, the default output is a shared resource between all my threads), for example pointers, buffers, data structures, etc. You must be very careful to lock some pieces of code to ensure that the shared objects or shared memory must be accessed just for a thread at a given time.
To ensure this, the C++11 standard library provide us with a mutex class that we can use in a multithreaded scenario to lock pieces of code.
Look at my example modified once more:
#include <iostream> #include <thread> #include <mutex> using namespace std; void run(int n, mutex& m) { for (auto i = 0; i < 5; i++) { m.lock(); cout << n << ": " << i << endl; m.unlock(); } } int main() { mutex m; thread t1(run, 1, ref(m)); thread t2(run, 2, ref(m)); thread t3(run, 3, ref(m)); t1.join(); t2.join(); t3.join(); return 0; }
In my example, I have added a shared mutex and I am passing it as a reference to the thread function. I am invoking to the lock()
method before the code I want to be executed by only a thread at a time, and after that I am invoking to unlock()
to make the block of code available to other threads.
What do you think about the STL thread library?
Nicee, Thank you so much for this. I just learned new stuff. Keep making this kind of article please :D
Thanks for reading it!