Effective STL 第12條:切勿對STL容器的線程安全性有不切實際的依賴

在多線程中,對一個STL實現,我們最多隻能期望:

1、多個線程讀是安全的。多個線程可以同時讀同一個容器的內容,並且保證是正確的。自然的,在讀的過程中,不能對容器有任何寫入操作。

2、多個線程對不同的容器做寫入操作是安全的。多個線程可以同時對不同的容器做寫入操作。


在一般情況下,我們可以有下面的解決方法:

1、自己手工做同步控制。

		vector<int> v;
		...
		getMutexFor(v);
		vector<int>::iterator first5(find(v.begin(), v.end(), 5));
		if (first5 != v.end())
		{
			*first5 = 0;
		}
		releaseMutexFor(v);


   2、相對上面的做法,更爲面向對象的方法是創建一個Lock類,它在構造函數中獲得一個互斥體,在析構函數中釋放它,從而儘可能地減少getMutexFor調用沒有相對應得releaseMutexFor調用的可能性。這樣的類(實際上是一個類模板)看起來大概像這樣:

		template<typename Container>                  	//一個爲容器獲取和釋放互斥體的模板
		class Lock					//框架:其中的很多細節被省略了
		{
		public:
			Lock(const Container& container) :
			c(container)
			{
				getMutexFor(c);			//在構造函數中獲取互斥體
			}
			~Lock()
			{
				releaseMutexFor(c);		//在析構函數中釋放它
			}
		private:
			const Container & c;
		}

使用類(如Lock)來管理資源的生存期的思想通常被稱爲“獲取資源時即初始化”(resource acquisition is initialization)。在任何一本C++書中都可以學習參考。但是,要記住,上面的例子只是給出了一個框架,實際開發中還需要一系列的增強。

		vector<int> v;
		...
		{					//創建新的代碼塊
			Lock<vector<int> > lock(v);      	//獲取互斥體
			vector<int>::iterator first5(find(v.begin(), v.end(), 5));
			if (first5 != v.end()) {
				*first5 = 0;
			}
		}					//代碼塊結束,自動釋放互斥體


因爲Lock對象在其析構函數中釋放容器的互斥體,所以很重要的一點是,當互斥體應該被釋放時Lock就要被析構。爲了做到這一點,我們創建了一個新的代碼塊(block),在其中定義了Lock,當不再需要互斥體時就結束該代碼塊。看起來好像是我們把“調用releaseMutexFor”這一任務換成了“結束代碼塊”,事實上這種說法是不確切的。如果我們忘了爲Lock創建新的代碼塊,則互斥體仍然會被釋放,只不過是晚一些——當控制到達包含block的代碼塊末尾時。而如果我們忘了調用releaseMutexFor,那麼我們永遠也不會釋放互斥體。


而且,基於Lock的方案在有異常發生時也是強壯的。C++保證,如果有異常被拋出,局部對象會被析構,所以,即便在我們使用Lock對象的過程中有異常拋出,Lock仍會釋放它所擁有的互斥體。如果我們依賴手工調用getMutexFor和releaseMutexFor,那麼,當在調用getMutexFor之後而在調用releaseMutexFor之前異常拋出時, 我們將永遠也無法釋放互斥體。




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