在多線程中,對一個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之前異常拋出時, 我們將永遠也無法釋放互斥體。