Threads in C++
Do not miss this exclusive book on Binary Tree Problems. Get it now for free.
Reading time: 35 minutes | Coding time: 10 minutes
Threads are generally referred as light weight processes. Each thread executes different parts of a program. Each thread shares memory,file descriptors and other system resources. In simple words, every process start with main thread and in that main thread we creates multiple threads
In main function we can create a thread which will have a entry point function the threadFunction() and we can wait for the thread to get completed and when the entry point function gets completed the control goes back to the main thread.
Syntax
#include <thread>
int main(){
thread t1;
}
We just need to include the thread library to create a thread. A simple example would be writing thread t1
to create a thread named t1, additional options for the created thread can be passed as arguments to the constrctor already defined in the thread library.
Example -
thread t1 (threadFunction, valueToThreadFunction)
Multithreading
Multithreading means two or more threads running concurrently where each thread is handling a different task. In simple words multithreading is multitasking that allows computer to run two or more programs concurrently.In general, there are two types of multitasking: process-based and thread-based.
- Process-based multitasking handles the concurrent execution of programs.
- Thread-based multitasking deals with the concurrent execution of pieces of the same program.
C++ does not contain any built-in support for multithreaded applications.Instead,it relies entirely upon the operating system to provide this feature.
Creation of thread
When creating a thread,something need to be passed to be executed on it.A few things that can be passed to a thread:
- Free functions
- member functions
- Functor objects
- Lambda expressions
Free function
Free function example - executes a function on a separate thread
#include <string>
#include <iostream>
#include <thread>
using namespace std;
void threadFunction(int value){
cout<<"value->"<<value<<endl;
}
int main(){
//creating thread and passing argument to Thread
thread t1(threadFunction,1000);
//wait for thread t1 to complete
t1.join();
return 0;
}
Member function
Member function example - executes a member function on a separate thread
#include <iostream>
#include <thread>
using namespace std;
class fun
{
public:
void threadFunction(int value)
{
cout <<"value->"<<value <<endl;
}
};
int main()
{
fun threadFunction;
// Create and execute the thread
thread t1(&fun::threadFunction, &threadFunction, 10);
// The member function will be executed in a separate thread
// Wait for the thread to finish, this is a blocking operation
t1.join();
return 0;
}
Functor object
Functor object example
#include <iostream>
#include <thread>
using namespace std;
class fun
{
public:
void operator()(int value)
{
cout<<"value->"<<value <<endl ;
}
};
int main()
{
fun threadFunction;
// Create and execute the thread
thread t1(threadFunction, 10);
// The functor object will be executed in a separate thread
// Wait for the thread to finish, this is a blocking operation
t1.join();
return 0;
}
Lambda expression
Lambda expression example
#include <iostream>
#include <thread>
using namespace std;
int main()
{
auto lambda = [](int value) { cout<<"value->"<<value <<endl ; };
// Create and execute the thread
thread t1(lambda, 10);
// The lambda expression will be executed in a separate thread
// Wait for thread to finish, this is a blocking operation
t1.join();
return 0;
}
Race condition
A race condition occurs when two or more threads can access shared data and they try to change it at the same time.In simple words, When two or more threads perform a set of operations in parallel, that access the same memory location. Also, one or more thread out of them modifies the data in that memory location, then this can lead to an unexpected results some times.Let's try to understand it with an example
class Wallet
{
int Money;
public:
Wallet() :Money(0){}
int getMoney() { return Money; }
void addMoney(int money){
for(int i = 0; i < money; ++i){
Money++;
}
}
};
Now Let’s create 5 threads and all these threads will share a same object of class Wallet and add 1000 to internal money using it’s addMoney() member function in parallel.So if initially money in wallet is 0. Then after completion of all thread’s execution money in Wallet should be 5000.But as all threads are modifying the shared data at same time, it might be possible that in some scenarios money in wallet at end will be much lesser than 5000.
int testMultithreadedWallet(){
Wallet walletObject;
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i){
threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));
}
for(int i = 0; i < threads.size() ; i++){
threads.at(i).join();
}
return walletObject.getMoney();
}
int main(){
int val = 0;
for(int k = 0; k < 1000; k++){
if((val = testMultithreadedWallet()) != 5000){
std::cout << "Error at count = "<<k<<" Money in Wallet = "<<val << std::endl;
}
}
return 0;
}
As addMoney() member function of same Wallet class object is executed 5 times hence it’s internal money is expected to be 5000. But as addMoney() member function is executed in parallel hence in some scenarios Money will be much lesser than 5000 i.e. output is
Error at count = 971 Money in Wallet = 4568 Error at count = 971 Money in Wallet = 4568 Error at count = 972 Money in Wallet = 4260 Error at count = 972 Money in Wallet = 4260 Error at count = 973 Money in Wallet = 4976 Error at count = 973 Money in Wallet = 4976
This is a race condition, as here two or more threads were trying to modify the same memory location at same time and lead to unexpected result.
How to fix Race Conditions?
To fix this problem we need to use Lock mechanism i.e. each thread need to acquire a lock before modifying or reading the shared data and after modifying the data each thread should unlock the Lock.
Mutex
The mutex class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.C++ offers a selection of mutex classes:
- std::mutex - offers simple locking functionality.
- std::timed_mutex - offers try_to_lock functionality
- std::recursive_mutex - allows recursive locking by the same thread.
- std::shared_mutex, std::shared_timed_mutex - offers shared and unique lock
functionalities
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <chrono>
using namespace std;
void ThreadFn (mutex &mtx){
lock_guard<mutex> lock(mtx);
cout<<"Mutex is locked"<<endl;
this_thread::sleep_for(chrono::seconds(5));
}
int main(){
mutex mtx;
thread th(ThreadFn, ref(mtx));
this_thread::sleep_for(chrono::seconds(1));
unique_lock<mutex> lock(mtx);
cout<<"Main thread active"<<endl;
th.join();
return 0;
}
Firstly thread th() gets executed and then it sleeps for 1sec avoiding any race condition.When thread th() sleeps the mutex lock get successful in ThreadFn which print "Mutex is locked" and the thread sleeps for 5sec.In the mean time unique_lock try to lock the mutex but it cannot locked the mutex because lock_guard locked the mutex so it will wait for 5sec and then unlock the mutex.As soon as mutex is unlocked it get locked by Unique_lock and print "main thread is active".Thus, the mutual exclusion works in this fashion.
Sign up for FREE 3 months of Amazon Music. YOU MUST NOT MISS.