c:c_threads:protected_access_to_shared_data_or_shared_resources:mutex:class_example
C - C++ Threads - Protected access to shared data or shared resources - Mutex - Class 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 within 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 the lockGuard Object will be called. // In the destructor it unlocks the mutex. } };
c/c_threads/protected_access_to_shared_data_or_shared_resources/mutex/class_example.txt · Last modified: 2021/06/08 09:34 by peter