Thread Synchronization in C++
Last Updated :
01 Jan, 2024
In C++ multithreading, synchronization between multiple threads is necessary for the smooth, predictable, and reliable execution of the program. It allows the multiple threads to work together in conjunction by having a proper way of communication between them. If we do not synchronize the threads working on a shared resource properly, it may cause some of the concurrency problems.
Importance of Thread Synchronization
Consider that there is a program that is being used for managing the banking data. Now, assume that two threads are being used for credit and debit in a bank account. Now, assume the following sequence of transactions of a user:
- Initial Balance = 300
- Credited_process: 400, Current Balance = 700
- Debited_process: 500, Current Balance = 200
Now, assume that there is no synchronization between the processes running on the different threads. So, it is possible that the sequence of the operation can be:
- Initial Balance: 300
Debited_process: 500, Current Balance = 300- Credit_process: 400, Current Balance = 700
Due to this change in the sequence, the user wont be able to withdraw his/her money from the bank account even though he had credited enough money in the account. Here, both credit_process and debit_process threads can be synchronized so that when there is a simultaneous credit and debit requests, it should first execute the credit request.
There can be many such concurrency issues that arises due to non-synchronized operations. Some of them are:
- Race Condition
- Deadlocks
- Starvation
Thread Synchronization in C++
In C++, thread synchronization is possible using the following methods:
1. Mutex in C++
Mutex is a synchronization primitive that locks the access to the shared resource if some thread is already accessing it.
Example
Let us take an example of a code snippet given below -
C++
// C++ program to illustrate the execution of multithreading
// program without any synchronization
#include <iostream>
#include <thread>
using namespace std;
// shared data
double val = 0;
int cnt = 0;
void add(double num)
{
val += num;
cnt++;
cout << "Thread " << cnt << ": " << val << endl;
}
// driver code
int main()
{
thread t1(add, 300);
thread t2(add, 600);
t1.join();
t2.join();
cout << "After addition : " << val << endl;
return 0;
}
Output
Thread Thread 22: : 900900
After addition : 900
In the above code snippet, we created two threads and we have an add function which in turn adds the values. Both the threads, t1 and t2 go to the add function simultaneously. Now, it becomes difficult to decide which thread will execute first and modify the value of num as both of them move to the add function simultaneously. Such a condition is called race condition. Thus, we should apply thread synchronization to avoid this race condition.
Here, we will apply mutex to achieve synchronization.
C++
// C++ program to illustrate the use of mutex locks to
// synchronize the threads
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
// shared lock
double val = 0;
// mutex lock
mutex m;
int cnt = 0;
void add(double num)
{
m.lock();
val += num;
cnt++;
cout << "Thread " << cnt << ": " << val << endl;
m.unlock();
}
// driver code
int main()
{
thread t1(add, 300);
thread t2(add, 600);
t1.join();
t2.join();
cout << "After addition : " << val << endl;
return 0;
}
Output
Thread 1: 300
Thread 2: 900
After addition : 900
or
Thread 1: 600
Thread 2: 900
After addition : 900
Note: In the above code after applying mutex we can get any of the two outputs as shown above. This is because, after applying mutex we have prevented both the threads from entering inside the add() function together, but, either thread t1 or thread t2 enters the add() function first and therefore the output varies with respect to that.
Now, both the threads will not be able to access the critical section at the same time as we have applied lock . Thus, here using mutex we were able to achieve thread synchronization.
2. Condition Variable in C++
The condition variable is another such synchronization primitive but it is mainly used to notify the threads about the state of the shared data. It is used with the mutex locks to create processes that automatically wait and notify the state of the resource to each other.
Example
C++
// C++ program to illustrate the use of condition variable
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
// condition variable and mutex lock
condition_variable cv;
mutex m;
// shared resource
int val = 0;
void add(int num)
{
lock_guard<mutex> lock(m);
val += num;
cout << "After addition: " << val << endl;
cv.notify_one();
}
void sub(int num)
{
unique_lock<mutex> ulock(m);
cv.wait(ulock,
[] { return (val != 0) ? true : false; });
if (val >= num) {
val -= num;
cout << "After subtraction: " << val << endl;
}
else {
cout << "Cannot Subtract now!" << endl;
}
cout << "Total number Now: " << val << endl;
}
// driver code
int main()
{
thread t2(sub, 600);
thread t1(add, 900);
t1.join();
t2.join();
return 0;
}
Output
After addition: 900
After subtraction: 300
Total number Now: 300
Explaination
Here, in this program we first create two threads and then we try to perform addition first followed by subtraction. But as we can see in the above program, we passed the thread t2 first and then t1. Assuming thread t2 goes to the sub() function first, it first locks the mutex and then checks the condition whether the val is 0 or not. As the val is 0 initially, the predicate returns false and as soon as it returns false, it releases the mutex and waits for the condition to be true i.e., val!=0. Now as the mutex is released, addition is performed in the add() function and after that notify_one() gets executed which notifies the waiting thread which in turn tries to get the lock and again checks the condition. This way the process continues.
One of the best use case of condition variable is Producer-Consumer Problem.
3. Promise and Future
The std::future and std::promise are used to return the data from a task executed on the other thread. The std::promise is used to sent the data and the std::future is used to receive the data on the main process. The std::future get() method can be used to retrieve the data returned by the process and is able to hold the current process till the value is returned.
This method is generally preferred of over the condition variable when we only want the task to be executed once.
Example
C++
// C++ program to illustrate the use of std::future and
// std::promise in thread synchronization.
#include <future>
#include <iostream>
#include <thread>
using namespace std;
// callable
void EvenNosFind(promise<int>&& EvenPromise, int begin,
int end)
{
int evenNo = 0;
for (int i = begin; i <= end; i++) {
if (i % 2 == 0) {
evenNo += 1;
}
}
EvenPromise.set_value(evenNo);
}
// driver code
int main()
{
int begin = 0, end = 1000;
promise<int> evenNo;
future<int> evenFuture = evenNo.get_future();
cout << "My thread is created !!!" << endl;
thread t1(EvenNosFind, move(evenNo), begin, end);
cout << "Waiting..........." << endl;
// detting the data
cout << "The no. of even numbers are : "
<< evenFuture.get() << endl;
t1.join();
return 0;
}
Output
My thread is created !!!
Waiting...........
The no. of even numbers are : 501
Here, in the above program, we try to find the number of even numbers in the given range. We first create a promise object and then we create a future object from that promise object. We send the promise object to the thread and then once we are ready with the value (after the function has been executed) ,we set the promise object. Then we create a future object from that promise. Finally we get the output from the future object to get our answer.
Conclusion
Thread Synchronization is necessary in the cases when we have multiple threads working on the shared data(critical section) so that the behaviour is defined. We have seen few common method of thread synchronization in C++. Although, it can be done manually using naive and bruteforce methods like (continuously updated some flag when done when done working on the shared data), the above discussed method provides more refined and tested ways of thread synchronization in C++.
Similar Reads
Thread Pool in C++
The Thread Pool in C++ is used to manage and efficiently resort to a group (or pool) of threads. Instead of creating threads again and again for each task and then later destroying them, what a thread pool does is it maintains a set of pre-created threads now these threads can be reused again to do
6 min read
thread_local Storage in C++ 11
Thread local storage (TLS) is a feature introduced in C++ 11 that allows each thread in a multi-threaded program to have its own separate instance of a variable. In simple words, we can say that each thread can have its own independent instance of a variable. Each thread can access and modify its ow
3 min read
Multithreading in C++
Multithreading is a technique where a program is divided into smaller units of execution called threads. Each thread runs independently but shares resources like memory, allowing tasks to be performed simultaneously. This helps improve performance by utilizing multiple CPU cores efficiently. Multith
6 min read
Multithreading in C
A thread is a single sequence stream within a process. Because threads have some of the properties of processes, they are sometimes called lightweight processes. But unlike processes, threads are not independent from each other unlike processes. They share with other threads their code section, data
9 min read
Thread hardware_concurrency() function in C++
Thread::hardware_concurrency is an in-built function in C++ std::thread. It is an observer function which means it observes a state and then returns the corresponding output. This function returns the number of concurrent threads supported by the available hardware implementation. This value might n
1 min read
Condition Variables in C++ Multithreading
In C++, the condition variable is a synchronization primitive that is used to notify the other threads in a multithreading environment that the shared resource is free to access. It is defined as the std::condition_variable class inside the <condition_variable> header file. Prerequisite: C++ M
3 min read
shared_ptr in C++
std::shared_ptr is one of the smart pointers introduced in C++11. Unlike a simple pointer, it has an associated control block that keeps track of the reference count for the managed object. This reference count is shared among all the copies of the shared_ptr instances pointing to the same object, e
5 min read
std::promise in C++
In C++ multithreading, a thread is the basic unit that can be executed within a process and to communicate two or more threads with each other, std::promise in conjunction with std::future can be used. In this article, we will discuss the std::promise in C++ and how to use it in our program. What is
3 min read
std::shared_mutex in C++
In C++, std::mutex is a mechanism that locks access to the shared resource when some other thread is working on it so that errors such as race conditions can be avoided and threads can be synchronized. But in some cases, several threads need to read the data from shared resources at the same time. H
4 min read
Deadlock in Single Threaded Java Application
Deadlock describes a situation where two or more threads are blocked forever because they are waiting for each other. There are many causes of deadlocks. Two or more threads can deadlock when the following conditions hold: Threads that are already holding locks request for new locks.The requests for
3 min read