To create a thread object, we use the std::thread class template from a header file. Once defined, the thread starts executing. To create a thread that executes a code inside a function, we supply the function name to the thread constructor as a parameter. Example:
#include
#include
void function1()
{
for (int i = 0; i < 10; i++)
{
std::cout << "Executing function1." << '
';
}
}
int main()
{
std::thread t1{ function1 }; // create and start a thread
t1.join(); // wait for the t1 thread to finish
}
Here we have defined a thread called t1 that executes a function function1. We supply the function name to the std::thread constructor as a first parameter. In a way, our program now has a main thread, which is the main() function itself, and the t1 thread, which was created from the main thread. The .join() member function says: “hey, main thread, please wait for me to finish my work before continuing with yours.” If we left out the .join() function, the main thread would finish executing before the t1 thread has finished its work. We avoid this by joining the child thread to the main thread.
If our function accepts parameters, we can pass those parameters when constructing the std::thread object:
#include
#include
#include
void function1(const std::string& param)
{
for (int i = 0; i < 10; i++)
{
std::cout << "Executing function1, " << param << '
';
}
}
int main()
{
std::thread t1{ function1, "Hello World from a thread." };
t1.join();
}
We can spawn multiple threads in our program/process by constructing multiple std::thread objects. An example where we have two threads executing two different functions concurrently/simultaneously:
#include
#include
void function1()
{
for (int i = 0; i < 10; i++)
{
std::cout << "Executing function1." << '
';
}
}
void function2()
{
for (int i = 0; i < 10; i++)
{
std::cout << "Executing function2." << '
';
}
}
int main()
{
std::thread t1{ function1 };
std::thread t2{ function2 };
t1.join();
t2.join();
}
This example creates two threads executing two different functions concurrently.
The function1 code executes in a thread t1, and the function2 code executes in a separate thread called t2.
We can also have multiple threads executing code from the same function concurrently:
#include
#include
#include
void myfunction(const std::string& param)
{
for (int i = 0; i < 10; i++)
{
std::cout << "Executing function from a " << param << '
';
}
}
int main()
{
std::thread t1{ myfunction, "Thread 1" };
std::thread t2{ myfunction, "Thread 2" };
t1.join();
t2.join();
}
Threads sometimes need to access the same object. In our example, both threads are accessing the global std::cout object in order to output the data. This can be a problem. Accessing the std::cout object from two different threads at the same time allows one thread to write a little to it, then another thread jumps in and writes a little to it, and we can end up with some strange text in the console window:
Executi.Executingng function1.Executing function2.
This means we need to synchronize the access to a shared std::cout object somehow. While one thread is writing to it, we need to ensure that the thread does not write to it.
We do so by locking and unlocking mutex-es. A mutex is represented by std::mutex class template from a header. A mutex is a way to synchronize access to shared objects between multiple threads. A thread owns a mutex once it locks the mutex, then performs access to shared data and unlocks the mutex when access to shared data is no longer needed. This ensures only one thread at the time can have access to a shared object, which is std::cout in our case.