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.