Threads
Creating Threads
File: thread1.cpp
#include <iostream> #include <thread> void thread_function() { std::cout << "thread function\n"; } int main() { std::thread t1( &thread_function ); // t starts running std::cout << "main thread\n"; t1.join(); // main thread waits for the thread t to finish return 0; }The above code shows how we can create a thread and run it.
We first define our function that the thread object will run.
void thread_function()
{
cout << "thread function\n";
}
Note there is at least always one thread running in a process.
We get the second ( child ) thread going with the statment:
thread t1( &thread_function ); // t1 starts running
cout << "main thread\n";
t1.join(); // main thread waits for the thread t1 to finish
Upon definition the thread "t1" starts running. The main thread
is also running. So we have 2 threads running at the same time.
In the "main" function we have the statement
t1.join(); // main thread waits for the thread t1 to finish
This blocks the main thread till the "t1" thread finishes. Otherwise
the main thread can exit ending the program and the "t1" thread may
not have finished.
We have many options and not just a standalone function as to
what the thread object may run. The below program shows some
of these options.
Different ways of creating threads.
File: thread2.cpp
#include <chrono> #include <iostream> #include <thread> #include <utility> using namespace std ; void f1(int n) { for (int i = 0; i < 5; ++i) { cout << "Thread f1 executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } void f2(int& n2) { for (int i1 = 0; i1 < 5; ++i1) { cout << "Thread f2 executing: " << i1 << "\n"; ++n2; this_thread::sleep_for(chrono::milliseconds(10)); } n2 = 101 ; } class foo { public: void bar() { for (int i = 0; i < 5; ++i) { cout << "Thread bar executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } int n = 0; }; class baz { public: void operator()() { for (int i = 0; i < 5; ++i) { cout << "Thread baz executing\n"; ++n; this_thread::sleep_for(chrono::milliseconds(10)); } } int n = 0; }; int main() { int n1 = 0; foo f; baz b; thread t1; // t1 does not run anything thread t2(f1, n1 + 1); // pass by value //Thread 2 thread t3(f2, ref(n1) ); // pass by reference this_thread::sleep_for(chrono::milliseconds(1)); cout << "In main about to shift t3 to t4:" << endl ; thread t4( move(t3) ); // t4 is now running f2(). t3 is no longer a thread thread t5(&foo::bar, &f); //executing a method in a class thread t6(b); //Executing a functor t2.join(); t4.join(); t5.join(); t6.join(); cout << "The value of n1: " << n1 << endl ; }
Output:
$ rm a.exe ; g++ thread2.cpp ; ./a.exe
Thread f1 executing
Thread f2 executing: 0
Thread f2 executing: 1
Thread f1 executing
In main about to shift t3 to t4:
Thread bar executing
Thread baz executing
Thread f2 executing: 2
Thread f1 executing
Thread f2 executing: 3
Thread f1 executing
Thread bar executing
Thread baz executing
Thread f1 executing
Thread f2 executing: 4
Thread baz executing
Thread bar executing
Thread baz executing
Thread bar executing
Thread bar executing
Thread baz executing
The value of n1: 101
Notice that if you run the program multiple times the output is
different every time and that's because the amount of time the
CPU spends on each thread is not deterministic.
std::thread t1;
If we don't provide anything to the thread object then nothing
is run. This is used in certain cases when we want to create
a thread object that can be assigned another thread later on.
Ex:
t1 = std::thread{ func1, arg1 };
std::thread t2(f1, n1 + 1); // pass by value
We are executing the function "f1" and passing it an argument by
value.
std::thread t3(f2, std::ref(n1)); // pass by reference
We need to use "std::ref" to make sure we are passing the
variable by reference. If we don't use "ref" then we get a
compiler error. At the end of "main" we print out the value
of "Ajay" to confirm that it has been changed.
this_thread::sleep_for(chrono::milliseconds(1));
cout << "In main about to shift t3 to t4:" << endl ;
std::thread t4( std::move(t3) );
// t4 is now running f2(). t3 is no longer a thread
In the above we are transferring the "t3" contents to a new
thread object "t4" .
Execution picks up where we left off in t3. We can see that by
having the main thread sleep for some time jus before the shift
and check the values of the "f2" function that are printed out.
std::thread t5(&foo::bar, &f);
//executing a method in a class
Here we are executing a method in a class and we need to provide
an object of the class as well.
std::thread t6(b);
In the above we are executing a functor in a thread.
Notice that the output is not quite clean as different
threads print. The "cout" is not thread safe. In the next
section we shall study how we can control this.
Exercise
1) Fill the TO DO parts in the following program. How can we tell that the threads are running at the same time ? Run the program a few times. Why is the output different each time ? Remove the "join" statements and explain the output.File: create_ex_1.cpp
#include <iostream> #include <thread> using namespace std ; void thread_function1( int id ) { for( int i1=0 ; i1<5 ; i1++ ) cout << "Thread id: " << id << } int main() { //TO DO Create 3 threads that call "thread_function1" //with the id's passed as 1 2 and 3 //TO DO Add the join statements so that the //main waits for the threads to finish cout << "main thread\n"; return 0; }2) Fill the "TO DO" parts in the following program.
File: create_ex_2.cpp
#include <iostream> #include <thread> using namespace std ; class Functor1 { public: void operator()(int delay) { cout << "Inside the start of Functor1 method." << endl ; this_thread::sleep_for(chrono::milliseconds(delay)); cout << "Inside the end of Functor1 method." << endl ; } }; int main() { //TO DO Create 2 threads using the Functor1 class //. Pass some delay. Observer the output. Functor1 Functor1Object ; //TO DO Add the join statements so that the //main waits for the threads to finish cout << "main thread\n"; return 0; }
Solutions
File: create_ex_1s.cpps.cpp
File: create_ex_2s.cpps.cpp