4.3安全執行緒介面(Thread-Safe Interface)
1.問題
多執行緒元件通常包括多個可被公用訪問的介面方法以及可以改變組件狀態的私人方法。為了避免出現競爭條件,可以使用一個組件內部的鎖對訪問其狀態的介面方法調用序列化。儘管當每個方法都自包容的時候,這種設計方法能很好地工作。但組件方法可能會相互調用完成計算任務。如果是這樣,在多執行緒元件中有以下幾個強制條件尚未解決,它們使用了錯誤的組件間方法調用的設計方法:
1)應該使安全執行緒組件避免“自死結”。當一個組件方法在該組件中獲得一個非遞迴鎖,然後調用試圖獲得同一個鎖的另一個組件方法時,會發生自死結。
2)為了避免出現對組件狀態的競爭條件等情況,應該使安全執行緒組件具有最小的加鎖開銷。如果為避免上面介紹的自死結問題而選擇一個遞迴組件鎖,由於組件間方法調用多次獲得和釋放鎖,而會產生不必要的開銷。
2.解決方案
按照以下兩個約定將處理組件間方法調用的所有組件初始化:
1)介面方法檢查。所有介面方法(如C++公有方法)應該只獲得/釋放組件鎖,在組件的“邊緣”進行同步檢查。獲得一個鎖後,介面方法立即轉寄給一個實現方法,後者執行實際的方法功能。實現方法返回後,介面在將控制返回給調用者之前釋放該鎖。
2)實現方法信任。只有被介面方法調用,實現方法(如C++的私人或保護方法)才能完成它們的任務。因此它們相信只有在獲得鎖後才會調用它們,它們本身不應該獲得或釋放鎖。實現方法也不應該“向上”調用介面方法,因為介面要獲得鎖。
3.實現
1)確定介面和相應的實現方法。介面方法定義組件的公用API,為每個介面方法對應地定義一個實現方法。
2)對介面和實現方法進行編程。
4.結論
優點:
1)提高了健壯性。由於使用組件間方法調用,該模式可以避免自死結。
2)改善了效能。該模式確保在必要時才獲得或釋放鎖。
3)簡化了軟體。將加鎖和功能特性問題分開,可以使加鎖和功能特性得到簡化。
不足:
1)增加間接的和額外的方法。
2)潛在的死結。
3)潛在的誤用。
4)潛在的開銷。
4.4雙檢查加鎖最佳化(double-checked locking optimization)
1.問題
序列化加鎖方法對於只要求執行一次初始化的對象或組件是不合適的,例如Singleton中的臨界區代碼在其初始化階段必須執行一次。但是對Singleton的每個方法調用都要擷取和釋放互斥鎖,從而過多地增加了開銷,為避免這些開銷,並發應用的程式員可能轉而使用全域變數而不是單件模式。不過這種解決方案有兩個缺點:
·可移植性不好。因為通常沒有指定在不同檔案中定義的全域對象被構造的順序。
·資源的浪費。因為即使不使用全域變數,也要產生全域變數。
2.解決方案
引入一個標誌,表示在獲得一個保證臨界區的鎖之前確定是否需要執行該臨界區。如果不需要執行該代碼,就跳過該臨界區,從而避免了不必要的加鎖開鎖。在臨界區內增加相同的標誌檢測,表示進入臨界區後是否需要初始化資源,從而避免了資源被多次初始化。
3.實現
1)確定要僅執行一次的臨界區。該臨界區進行的操作(如初始化邏輯)在程式中僅執行一次。
2)實現加鎖邏輯。加鎖邏輯對只執行一次的臨界區代碼的訪問序列化。
3)實現首次進入(first-time-in)標誌。該標誌表示臨界區是否被執行過。
4.結論
優點:
1)使加鎖開銷最小。
2)防止競爭條件。
不足:
1)非原子指標或整合賦值語義。
2)多處理器緩衝的連貫性。
3)額外的互斥使用。