1. 程式人生 > >C++11多執行緒程式設計 第五章: 使用鎖來解決竟態條件

C++11多執行緒程式設計 第五章: 使用鎖來解決竟態條件

C++11 Multithreading – Part 5: Using mutex to fix Race Conditions

Varun February 22, 2015 C++11 Multithreading – Part 5: Using mutex to fix Race Conditions2018-08-18T15:19:37+00:002 Comments

In this article we will discuss how to use mutex locks to protect shared data in multithreaded environment and avoid race conditions.

 

To fix race conditions in multi-threaded environment we need mutex i.e. each thread needs to lock a mutex before modifying or reading the shared data and after modifying the data each thread should unlock the mutex.

std::mutex

In the C++11 threading library, the mutexes are in the <mutex> header file. The class representing a mutex is the std::mutex class.

There are two important methods of mutex:
1.) lock()
2.) unlock()

We have explained Race condition using a Multithreaded Wallet in previous article i.e.

https://thispointer.com//c11-multithreading-part-4-data-sharing-and-race-conditions/

In this article we will see how to use std::mutex to fix the race condition in that multithreaded wallet.

As, Wallet provides a service to add money in Wallet and same Wallet object is used between different threads, so we need to add Lock in addMoney() method of the Wallet i.e.
Acquire lock before increment the money of Wallet and release lock before leaving that function. Let’s see the code,

Wallet class that internally maintains money and provides a service/function i.e. addMoney().
This member function, first acquires a lock then increments the internal money of wallet object by specified count and then releases the lock.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include<iostream>

#include<thread>

#include<vector>

#include<mutex>

 

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();

    }

};

Now Let’s create 5 threads and all these threads will share a same object of class Wallet and add 1000 to internal money using it’s addMoney() member function in parallel.

So, if initially money in wallet is 0. Then after completion of all thread’s execution money in Wallet should be 5000.

And this mutex lock guarantees that Money in the Wallet will be 5000 at end.

Let’s test this,

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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;

}

It’s guaranteed that it will not found a single scenario where money in wallet is less than 5000.
Because 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.

But what if we forgot to unlock the mutex at the end of function. In such scenario, one thread will exit without releasing the lock and other threads will remain in waiting.
This kind of scenario can happen in case some exception came after locking the mutex. To avoid such scenarios we should use std::lock_guard.

std::lock_guard 

std::lock_guard is a class template, which implements the RAII for mutex.
It wraps the mutex inside it’s object and locks the attached mutex in its constructor. When it’s destructor is called it releases the mutex.

Let’s see the code,

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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

// poin then destructor of lockGuard

// will be called due to stack unwinding.

//

mMoney++;

}

// Once function exits, then destructor

// of lockGuard Object will be called.

// In destructor it unlocks the mutex.

    }

};

 

ps:

這裡要注意防止死鎖產生. std::lock_guard 即是為了解決因為異常等的產生而導致的鎖沒有及時釋放.