This is an old revision of the document!
Table of Contents
C - C++ Threads - Protect shared data or shared resources with a mutex
In a multithreaded environment, more than one thread is often competing for a resource or shared data.
This often results in undefined behavior for the resource or data , unless the resource or data is protected using some mechanics that only allows ONE thread to act on it at a time.
In this example, std::cout is a shared resource that is shared by 6 threads (t1-t5 + main).
#include <iostream> #include <string> #include <thread> #include <mutex> std::mutex mu; void ShowMessage(std::string msg) { std::cout << "Thread " << std::this_thread::get_id() << " says " << msg << std::endl; } int main() { std::thread t1(ShowMessage, "Hello from Jupiter"); std::thread t2(ShowMessage, "Hello from Saturn"); std::thread t3(ShowMessage, "Hello from Mars"); ShowMessage("Hello from Main/Earth"); std::thread t4(ShowMessage, "Hello from Uranus"); std::thread t5(ShowMessage, "Hello from Neptune"); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); return 0; }
NOTE: The output will not be in any specific order.
This is because the five threads get the std::cout resource in a random fashion.
Solution
To make the output more deterministic, the solution is to protect the access to std::cout resource using a std::mutex.
Just change the ShowMessage() to acquire a mutex before using std::cout and release it after it is done.
void ShowMessage(std::string msg) { mu.lock(); std::cout << "Thread " << std::this_thread::get_id() << " says " << msg << std::endl; mu.unlock(); }
Another Example
#include <iostream> #include <thread> #include <vector> #include<mutex> // The Wallet Class provides a service to add money into a Wallet. // // The same Wallet object is used between different threads, so a Lock is needed in the addMoney() method of the Wallet. // // A lock is acquired before incrementing the money witin the Wallet and the lock is released before leaving that function. class Wallet { int mMoney; std::mutex mutex; public: Wallet() :mMoney(0){} int getMoney() { return mMoney; } void addMoney(int money) { mutex.lock(); for(int i = 0; i < money; ++i) { mMoney++; } mutex.unlock(); } }; // Five threads will share the same Wallet class object and each thread will add 1000 into the Wallet using the addMoney() member function in parallel. // // So, if initially the money in the wallet is 0, then then after completion of all the threads, the money in the Wallet should be 5000. // // Adding a mutex lock guarantees that Money in the Wallet will be 5000 at the end. 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; //break; } } return 0; }
NOTE: The mutex lock in addMoney() makes sure that once one thread finishes the modification of money then only any other thread modifies the money in Wallet.
WARNING: What happens if for some reason a thread that has locked the addMoney() is unable to unlock the mutex at the end of the function?
Other threads will remain in waiting, and this will not be successful.
This kind of scenario can happen if some exception arose after locking the mutex.
To avoid such scenarios we should use std::lock_guard.
- See the next note on std::lock_guard.
std::lock_guard
std::lock_guard is a class template, which implements the RAII for mutex.
It wraps the mutex inside its object and locks the attached mutex in its constructor.
When its destructor is called it releases the mutex.
class Wallet { int mMoney; std::mutex mutex; public: Wallet() :mMoney(0){} int getMoney() { return mMoney; } void addMoney(int money) { std::lock_guard<std::mutex> lockGuard(mutex); // In constructor it locks the mutex. for(int i=0; i<money; ++i) { // If some exception occurs at this // point then the destructor of lockGuard // will be called due to stack unwinding. // mMoney++; } // Once the function exits, then the destructor // of lockGuard Object will be called. // In the destructor it unlocks the mutex. } };