Open In App

Thread Synchronization in C++

Last Updated : 01 Jan, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

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:

  1. Initial Balance = 300
  2. Credited_process: 400, Current Balance = 700
  3. 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:

  1. Initial Balance: 300
  2. Debited_process: 500, Current Balance = 300
  3. 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:

  1. Race Condition
  2. Deadlocks
  3. 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

C++ Program to Show Thread Interface and Memory Consistency Errors
C++ allows Multithreading by using the 'thread' header file. The program acts as one thread but to increase program execution time/performance we can use threads to run parts of the program concurrently. But it may lead to issues of memory consistency errors and may not give us the proper output. Threads are used to improve the performance of appli
2 min read
Thread joinable() function in C++
Thread::joinable 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 and checks whether the thread object is joinable or not. A thread object is said to be joinable if it identifies/represent an active thread of execution. A thread is not joinable if: It wa
2 min read
Thread get_id() function in C++
Thread::get_id() 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 value of std::thread::id thus identifying the thread associated with *this.Syntax: thread_name.get_id(); Parameters: This function does not accept any parameters
2 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 not always be accurate. Syntax: thread::hardware_co
1 min read
Difference between Process and Kernel Thread
1. Process: Process is an activity of executing a program. Process is of two types - User process and System process. Process control block controls the operation of the process. 2. Kernel Thread: Kernel thread is a type of thread in which threads of a process are managed at kernel level. Kernel threads are scheduled by operating system (kernel mod
1 min read
How to wake up a std::thread while it is sleeping?
In this article, we will discuss how to wake up a std::thread while it is sleeping. It is known that a thread can't be exited when it is sleeping. So it is woken up using a command as: std::condition_variable Below is the pseudo-code to implement the same: C/C++ Code // Custom Class struct MyClass { // Constructor MyClass() : my_thread([this]() { t
3 min read
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 many tasks concurrently. By using this approach we
6 min read
How to Create a Thread in C++?
A thread is a basic element of multithreading which represents the smallest sequence of instructions that can be executed independently by the CPU. In this article, we will discuss how to create a thread in C++. How to Create a Thread in C++?In C++, the std::thread is a class template that is used to create and manage threads. Also, while creating
2 min read
How to Join a Thread in C++?
In C++, a thread is a basic element of multithreading that represents the smallest sequence of instructions that can be executed independently by the CPU. In this article, we will discuss how to join a thread in C++. How to Join a Thread in C++?Joining a thread is a means to wait for the thread to complete its execution before moving to the next pa
2 min read
How to Detach a Thread in C++?
In C++, a thread is a basic element of multithreading that represents the smallest sequence of instructions that can be executed independently by the CPU. In this article, we will discuss how to detach a thread in C++. What does Detaching a Thread mean?Detaching a thread means allowing the thread to execute independently from the thread that create
2 min read