對STL容器執行緒安全性的期待現實一些

2021-06-03 05:46:23 字數 2524 閱讀 4336

標準c++的世界是相當保守和陳舊的。在這個純潔的世界,所有可執行檔案都是靜態鏈結的。不存在記憶體對映檔案和共享記憶體。沒有視窗系統,沒有網路,沒有資料庫,沒有其他程序。在這種情況下,當發現標準沒有提到任何關於執行緒的東西時你不該感到驚訝。你對stl的執行緒安全有的第乙個想法應該是它將因實現而不同。

在stl容器(和大多數廠商的願望)裡對多執行緒支援的**規則已經由sgi定義,並且在它們的stl**[21]上發布。大體上說,你能從實現裡確定的最多是下列內容:

就這些了,那麼讓我解釋你可以期望的是什麼,而不是你可以確定的。有些實現提供這些保證,但是有些不。

寫多執行緒的**很難,很多程式設計師希望stl實現是完全執行緒安全的。如果是那樣,程式設計師可以不再需要自己做並行控制。毫無疑問這將帶來很多方便,但這也非常難實現。乙個庫可能試圖以下列方式實現這樣完全執行緒安全的容器:

現在考慮下列**。它搜尋乙個vector中第一次出現5這個值的地方,而且,如果它找到了,就把這個值改為0。

vectorv;

vector::iterator first5(find(v.begin(), v.end(), 5)); // 行1

if (first5 != v.end())

在多執行緒環境裡,另乙個執行緒可能在行1完成之後立刻修改v中的資料。如果是那樣,行2對first5和v.end的檢測將是無意義的,因為v的值可能和它們在行1結束時的值不同。實際上,這樣的檢測會產生未定義的結果,因為另一線程可能插在行1和行2之間,使first5失效,或許通過進行一次插入操作造成vector重新分配它的內在記憶體。(那將使vector全部的迭代器失效。關於重新分配行為的細節,參見條款14。)類似的,行3中對*first5的賦值是不安全的,因為另乙個執行緒可能在行2和行3之間執行,並以某種方式使first5失效,可能通過刪除它指向(或至少曾經指向)的元素。

在上面列舉的鎖定方法都不能防止這些問題。行1中begin和end呼叫都返回得很快,以至於不能提供任何幫助,它們產生的迭代器只持續到這行的結束,而且find也在那行返回。

要讓上面的**成為執行緒安全的,v必須從行1到行3保持鎖定,很難想象stl實現怎麼能自動推斷出這個。記住同步原語(例如,訊號燈,互斥量,等等)通常開銷很大,更難想象實現怎麼在程式沒有明顯效能損失的情況下做到前面所說的——以這樣的一種方式設計——讓最多乙個執行緒在1-3行的過程中能訪問 v。

這樣的考慮解釋了為什麼你不能期望任何stl實現讓你的執行緒悲痛消失。取而代之的是,你必須手工對付這些情況中的同步控制。 在這個例子裡,你可以像這樣做:

vectorv;

...getmutexfor(v);

vector::iterator first5(find(v.begin(), v.end(), 5));

if (first5 != v.end())

releasemutexfor(v);

乙個更物件導向的解決方案是建立乙個lock類,在它的建構函式裡獲得互斥量並在它的析構函式裡釋放它,這樣使getmutexfor和releasemutexfor的呼叫不匹配的機會減到最小。這樣的乙個類(其實是乙個類模板)基本是這樣的:

template// 獲取和釋放容器的互斥量

class lock

~lock()

private:

const container& c;

};

使用乙個類(像lock)來管理資源的生存期(例如互斥量)的辦法通常稱為資源獲得即初始化,你應該能在任何全面的c++教材裡讀到它。乙個好的開端是stroustrup的《the c++ programming language》[7],因為stroustrup普及了這個慣用法,但你也可以轉到《more effective c++》的條款9。不管你參考了什麼**,記住上述lock是被剝離到最原始的本質的。乙個工業強度的版本需要很多改進,但是那樣的擴充與stl無關。而且這個最小化的lock已經足夠看出我們可以怎麼把它用於我們一直考慮的例子:

vectorv;

...} // 關閉塊,自動

// 釋放互斥量

因為lock物件在lock的析構函式裡釋放容器的的互斥量,所以在互斥量需要釋放是就銷毀lock是很重要的。為了讓這件事發生,我們建立乙個裡面定義了lock的新塊,而且當我們不再需要互斥量時就關閉那個塊。這聽上去像我們只是用關閉新塊的需要換取了呼叫releasemutexfor的需要,但是這是錯誤的評價。如果我們忘記為lock建立乙個新塊,互斥量一樣會釋放,但是它可能發生得比它應該的更晚——當控制到達封閉塊的末端。如果我們忘記呼叫releasemutexfor,我們將不會釋放互斥量。

而且,這種基於lock的方法在有異常的情況下是穩健的。c++保證如果丟擲了異常,區域性物件就會被銷毀,所以即使當我們正在使用lock物件時有異常丟擲,lock也將釋放它的互斥量。如果我們依賴手工呼叫getmutexfor和releasemutexfor,那麼在呼叫 getmutexfor之後releasemutexfor之前如果有異常丟擲,我們將不會釋放互斥量。

異常和資源管理是重要的,但是它們不是本條款的主題。本條款是關於stl裡的執行緒安全。當涉及到執行緒安全和stl容器時,你可以確定庫實現允許在乙個容器上的多讀取者和不同容器上的多寫入者。你不能希望庫消除對手工並行控制的需要,而且你完全不能依賴於任何執行緒支援。

STL 執行緒安全性

stl 執行緒安全性 摘錄 在所有的主流stl實現方案中,幾乎所有的容器都是執行緒安全的 1 乙個執行緒讀寫乙個例項時,另乙個執行緒可以讀寫另乙個例項。2 多個執行緒可以在同時讀同乙個container。3 多個執行緒讀寫同乙個container時,你應該負責安排互斥性操作。乙個特例是std str...

STL容器的執行緒安全?

執行緒安全的情況 執行緒不安全的情況 看到風險了吧?在工程中多執行緒操作stl的場景應該還是比較常見的,乙個典型的例子就是用其來做生產者 消費者模型的佇列或者其他共享佇列,這樣為了應對執行緒安全問題我們必須自己對容器操作進行封裝。這是我自己實現的的封裝類threadsafe container.h,...

i 的執行緒安全性

i 的執行緒安全性可以總結如下 1 如果i是區域性變數,那麼是可重入的,也就是執行緒安全的。2 如果i是全域性變數,則同一程序的不同執行緒都可能訪問到該變數,因而是執行緒不安全的。上面這兩點比較清晰,具體原因我將在下面解釋 本質上來講,i並不是因為是全域性變數才說是執行緒不安全的。其實其本質原因是i...