[編程基礎] C++多線程入門5-使用互斥鎖解決資源競爭

原始C++標準僅支持單線程編程。新的C++標準(稱爲C++11或C++0x)於2011年發佈。在C++11中,引入了新的線程庫。因此運行本文程序需要C++至少符合C++11標準。

5 使用互斥鎖解決資源競爭

在本文中,我們將討論如何使用互斥鎖來保護多線程環境中的共享數據並避免資源競爭。爲了解決多線程環境中的資源競爭,我們需要互斥鎖,即每個線程都需要在修改或讀取共享數據之前鎖定互斥鎖,並且在修改數據之後,每個線程都應解鎖互斥鎖。

5.1 std::mutex

在C++11線程庫中,互斥鎖位於mutex頭文件中。表示互斥鎖的類是std::mutex類
互斥鎖有兩種重要的方法:

  1. lock()
  2. unlock()

我們已經在上一篇文章中使用多線程錢包解釋了資源競爭。在本文中,我們將看到如何使用std::mutex修復該多線程錢包中的資源競爭。由於電子錢包提供了在電子錢包中添加資金的服務,並且在不同線程之間使用了相同的電子錢包對象,因此我們需要在電子錢包的addMoney()方法中添加鎖定,即在增加電子錢包的貨幣之前獲取鎖並在離開該錢包之前釋放鎖功能。讓我們看一下代碼:
內部維護貨幣並提供服務/功能的錢包類,即addMoney()。
該成員函數首先獲取一個鎖,然後將錢包對象的內部貨幣增加指定的數量,然後釋放該鎖。

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

現在,讓我們創建5個線程,所有這些線程將共享Wallet類的同一對象,並使用其addMoney()成員函數並行向內部貨幣添加100000。因此,如果最初在錢包中的錢爲0。那麼在完成所有線程的執行後,在Wallet中的錢應該爲500000。並且此互斥鎖可確保電子錢包中的資金最終爲500000。讓我們測試一下:

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

int testMultithreadedWallet()
{
	Wallet walletObject;
	std::vector<std::thread> threads;
	for (int i = 0; i < 5; ++i) {
		threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
	}
	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 < 10; k++)
	{
		if ((val = testMultithreadedWallet()) != 500000)
		{
			std::cout << "Error at count = " << k << "  Money in Wallet = " << val << std::endl;
			//break;
		}
		else
		{
			std::cout << "Now count = " << k << "  Money in Wallet = " << val << std::endl;
			//break;
		}
	}
	return 0;
}

輸出爲:

Now count = 0  Money in Wallet = 500000
Now count = 1  Money in Wallet = 500000
Now count = 2  Money in Wallet = 500000
Now count = 3  Money in Wallet = 500000
Now count = 4  Money in Wallet = 500000
Now count = 5  Money in Wallet = 500000
Now count = 6  Money in Wallet = 500000
Now count = 7  Money in Wallet = 500000
Now count = 8  Money in Wallet = 500000
Now count = 9  Money in Wallet = 500000

可以保證不會發現錢包中的錢少於500000的單個情況。因爲addMoney中的互斥鎖可確保一旦一個線程完成了錢的修改,則只有其他任何線程才能修改Wallet中的錢。
但是,如果我們忘記在功能結束時解鎖互斥鎖,該怎麼辦?在這種情況下,一個線程將退出而不釋放鎖,而其他線程將保持等待狀態。如果鎖定互斥鎖後發生某些異常,則可能發生這種情況。爲了避免這種情況,我們應該使用std::lock_guard。

5.2 std::lock_guard

Lock_Guard是一個類模板,它實現了互斥鎖的RAII。它將互斥體包裝在其對象中,並將附加的互斥體鎖定在其構造函數中。當調用它的析構函數時,它會釋放互斥鎖。讓我們看看代碼:

#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)
	{
		// 在構造函數中,它鎖定互斥鎖 In constructor it locks the mutex
		std::lock_guard<std::mutex> lockGuard(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.
			// 如果在此位置發生異常,則由於堆棧展開,將調用lockGuard的析構函數。
			mMoney++;
		}
		// Once function exits, then destructor of lockGuard Object will be called. In destructor it unlocks the mutex.
		//一旦函數退出,則析構函數,將調用析構函數中的lockGuard對象,它解鎖互斥鎖。
	}
};

int testMultithreadedWallet()
{
	Wallet walletObject;
	std::vector<std::thread> threads;
	for (int i = 0; i < 5; ++i) {
		threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 100000));
	}
	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 < 10; k++)
	{
		if ((val = testMultithreadedWallet()) != 500000)
		{
			std::cout << "Error at count = " << k << "  Money in Wallet = " << val << std::endl;
			//break;
		}
		else
		{
			std::cout << "Now count = " << k << "  Money in Wallet = " << val << std::endl;
			//break;
		}
	}
	return 0;
}

輸出爲:

Now count = 0  Money in Wallet = 500000
Now count = 1  Money in Wallet = 500000
Now count = 2  Money in Wallet = 500000
Now count = 3  Money in Wallet = 500000
Now count = 4  Money in Wallet = 500000
Now count = 5  Money in Wallet = 500000
Now count = 6  Money in Wallet = 500000
Now count = 7  Money in Wallet = 500000
Now count = 8  Money in Wallet = 500000
Now count = 9  Money in Wallet = 500000

5.3 參考

https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章