Boost锁~临界区保护和临界资源共享
前言:
?????? 除了thread,boost::thread另一個重要組成部分是mutex,以及工作在mutex上的boost::mutex::scoped_lock、condition和barrier,這些都是為實現線程同步提供的。
原文鏈接:http://blog.csdn.net/yefengnidie/article/details/6428596
一、Boost鎖
mutex:
boost提供的mutex有6種:
boost::mutex????? 唯一鎖,同時只能由一個線程訪問,不分讀寫
boost::try_mutex
boost::timed_mutex
boost::recursive_mutex????????? 讀寫鎖
boost::recursive_try_mutex
boost::recursive_timed_mutex
一、 下面僅對boost::mutex進行分析。
mutex類是一個CriticalSection(臨界區)封裝類,它在構造函數中新建一個臨界區并InitializeCriticalSection,然后用一個成員變量
void*?m_mutex;
來保存該臨界區結構。
除此之外,mutex還提供了do_lock、do_unlock等方法,這些方法分別調用EnterCriticalSection、 LeaveCriticalSection來修改成員變量m_mutex(CRITICAL_SECTION結構指針)的狀態,但這些方法都是private的,以防止我們直接對mutex進行鎖操作,所有的鎖操作都必須通過mutex的友元類detail::thread::lock_ops<mutex>來完成,比較有意思的是,lock_ops的所有方法:lock、unlock、trylock等都是static的,如lock_ops<Mutex>::lock的實現:
template <typename Mutex> class lock_ops : private noncopyable{public:static void lock(Mutex& m){m.do_lock();} }
boost::thread的設計者為什么會這么設計呢?我想大概是:
2、雖然我們可以通過lock_ops來修改mutex的狀態,如:
#include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <boost/thread/detail/lock.hpp>int main() {boost::mutex mt;//mt.do_lock(); // Error! Can not access private member!boost::detail::thread::lock_ops<boost::mutex>::lock(mt);return 0; }
但是,這是不推薦的,因為mutex、scoped_lock、condition、barrier是一套完整的類系,它們是相互協同工作的,像上面這么操作沒有辦法與后面的幾個類協同工作。
?????? 上面說過,不應該直接用lock_ops來操作mutex對象,那么,應該用什么呢?答案就是scoped_lock。與存在多種mutex一樣,存在多種與mutex對應的scoped_lock:
scoped_lock
scoped_try_lock
scoped_timed_lock
這里我們只討論scoped_lock。
scoped_lock是定義在 namespace ?boost :: detail :: thread下的,為了方便我們使用(也為了方便設計者),mutex使用了下面的 typedef :
typedef ?detail :: thread :: scoped_lock < mutex > ?scoped_lock ;
這樣我們就可以通過:
boost :: mutex :: scoped_lock
來使用scoped_lock類模板了。
由于scoped_lock的作用:僅在于對mutex加鎖/解鎖(即使mutex EnterCriticalSection/LeaveCriticalSection),因此,它的接口也很簡單,除了構造函數外,僅有lock/unlock/locked(判斷是否已加鎖),及類型轉換操作符void*,一般我們不需要顯式調用這些方法,因為scoped_lock的構造函數是這樣定義的:
注:m_mutex是一個mutex的引用。
因此,當我們不指定initially_locked參數構造一個scoped_lock對象時,scoped_lock會自動對所綁定的mutex加鎖,而析構函數會檢查是否加鎖,若已加鎖,則解鎖;當然,有些情況下,我們可能不需要構造時自動加鎖,這樣就需要自己調用lock方法。后面的condition、barrier也會調用scoped_lock的lock、unlock方法來實現部分方法。
正因為scoped_lock具有可在構造時加鎖,析構時解鎖的特性,我們經常會使用局部變量來實現對mutex的獨占訪問。
#include <boost/thread/thread.hpp> #include <boost/thread/mutex.hpp> #include <iostream>boost::mutex io_mutex; void count() // worker function {for (int i = 0; i < 10; ++i){boost::mutex::scoped_lock lock(io_mutex);std::cout << i << std::endl;}}int main(int argc, char* argv[]){boost::thread thrd1(&count);boost::thread thrd2(&count);thrd1.join();thrd2.join();return 0; }
在每次輸出信息時,為了防止整個輸出過程被其它線程打亂,通過對io_mutex加鎖(進入臨界區),從而保證了輸出的正確性。
Java的synchronized可用于對方法加鎖,對代碼段加鎖,對對象加鎖,對類加鎖(仍然是對象級的),這幾種加鎖方式都可以通過上面講的對象鎖來模擬;相反,在Java中實現全局鎖好像有點麻煩,必須將請求封裝到類中,以轉換成上面的四種 synchronized形式之一。
condition
condition的接口如下:
class condition : private boost::noncopyable // Exposition only { public:// construct/copy/destructcondition();~condition();// notificationvoid notify_one();void notify_all();// waitingtemplate<typename ScopedLock> void wait(ScopedLock&);template<typename ScopedLock, typename Pred> void wait(ScopedLock&, Pred);template<typename ScopedLock>bool timed_wait(ScopedLock&, const boost::xtime&);template<typename ScopedLock, typename Pred>bool timed_wait(ScopedLock&, Pred); };其中wait 用于等待某個condition的發生,而timed_wait則提供具有超時的wait功能,notify_one用于喚醒一個等待該condition發生的線程,notify_all則用于喚醒所有等待該condition發生的線程。
由于condition的語義相對較為復雜,它的實現也是整個boost :: thread庫中最復雜的(對Windows版本而言,對支持pthread的版本而言,由于pthread已經提供了pthread_cond_t,使得condition實現起來也十分簡單),下面對wait和notify_one進行簡要分析。
condition內部包含了一個condition_impl對象,由該對象執行來處理實際的wait、notify_one ... 等操作
二、臨界資源保護(并行程序設計過程中)
??從事軟件研發工作有近兩年的時間了,從自己的感覺來說,系統軟件,特別是內核軟件開發的難點在于并發程序設計,或者從更深層次的角度來講應該是并行程序設計(多核程序設計)。并行程序設計的難點在于臨界資源的保護。通常各并行的線程或者是進程之間都會存在共享的臨界資源,如果這些臨界資源處理不當,那么小則程序運行出錯;大則系統崩潰。所以,我個人認為只要將臨界資源處理好,那么并行程序設計就不是問題了。
???????下面結合自己這一段時間的程序設計“經驗”,對并行程序設計過程中所應該注意的問題和一些方法做初步小結。
1、?避免臨界資源,減少臨界資源的數量
并行程序設計中,臨界資源越多,程序設計將會越復雜,所以在程序設計之初,需要考慮臨界資源的數量,盡可能的減少臨界資源。另一方面,一個資源在不同的時間點會呈現出不同的特點。在某些情況下其可能為臨界資源;在某些情況其表現為非臨界資源。例如,操作需要創建一個設備,該設備需要添加到共享資源區中,為了避免臨界資源的產生,可以先將創建設備的所有信息都初始化完畢之后,最后將設備添加到共享資源區,這樣在一定程度上避免了臨界資源,可以簡化創建設備的過程,提高系統性能。所以,從這一點上我們可以得出一個結論,在處理臨界資源時,很多時候可以將資源從總的資源池中取出來,讓被訪問的臨界資源成為一個非臨界資源,在短時間內為一個上下文獨享,這樣可以簡化設計,提高效率。但是,并不是所有的應有都允許這樣的操作,然而,總的原則應該是不變的,那就是盡可能的減少臨界資源的數量,減少并發程序的依賴關系,這是從“根”上簡化并發程序的設計與實現。
2、?函數的設計需要考慮應用上下文環境
并行程序中的函數設計并不僅僅是函數功能的封裝,算法的封裝,還需要考慮函數的應用上下文,也就是設計實現的函數將在什么環境下被調用。這一點非常重要,如果處理不好,那么很容易出現死鎖、系統崩潰等現象。
函數設計需要考慮應用上下文,這一點一個非常重要的原因在于并行程序,特別是內核程序中的上下文特點存在很大的區別。例如,Linux內核上下文分為普通的用戶進程上下文、軟中斷上下文、中斷上下文。在用戶進程上下文中,函數的限制條件不是很強烈,能夠睡眠的函數都可以運行,但是在中斷上下文限制條件很強烈,睡眠函數是不能運行的,所以,函數設計需要考慮應用上下文環境。
???????除了上述原因之外,另一個非常重要的原因在于加鎖的問題,而這個問題可能更容易在設計實現過程中被忽視。函數的運行上下文一定要考慮加鎖情況,例如一個函數在持有spinlock資源鎖的條件下調用一個可能引起睡眠的函數,那么系統肯定崩潰;另外,一個函數在持有鎖A的時候再次調用可能訪問鎖A的函數,那么系統肯定死鎖。總的來說,上述這些問題的根源都在于函數調用時沒有考慮運行上下文的加鎖情況,導致錯誤加鎖或者亂序加鎖。
???????因此,在并行程序設計過程中,設計一個函數需要考慮一下,這個函數是為哪個上下文寫的?調用這個函數存在哪些限制?這是在普通函數設計之上提出的更高要求。
3、?臨界資源的保護需要考慮讀寫性、競爭上下文
臨界資源的保護需要考慮對臨界資源訪問的讀寫性,如果訪問臨界資源的多個上下文存在讀操作,那么訪問臨界資源的鎖可以被優化。通常可以采用讀鎖對臨界資源進行讀訪問。另外,在臨界資源訪問時一定要考慮競爭上下文,如果競爭上下文為中斷上下文,那么需要考慮加鎖時間與可睡眠性,通常在Linux系統中采用Spinlock對其進行保護;如果競爭上下文為普通的進程上下文,那么保護的方法將簡單的多。
臨界資源保護時,鎖的設計非常重要,通常在設計實現過程中會遇到大鎖、小鎖的抉擇。大鎖的設計實現簡單,競爭點可以分析的非常清晰,但是程序效率將會大打折扣;小鎖的設計實現復雜,競爭點的分析、考慮將會變得復雜,程序實現效率將會大大提高。我個人認為,在設計臨界資源保護時,首先需要分析清楚競爭上下文,根據競爭上下文對資源訪問的競爭點分析結果,設計合理的鎖資源。盡可能在不太影響性能的前提下(鎖不能成為系統的性能瓶頸),設計大鎖資源。在后繼的性能優化過程中,如果有必要再將鎖資源進行必要細化。
???????并行程序設計中,臨界資源的訪問是程序設計的一大難點,一個好的程序設計人員,一定需要將程序的功能模塊切分好,程序的運行上下文及上下文之間的關系設計好,臨界資源及資源訪問的鎖設計好。只有這樣設計的程序才能具備一個完美的框架,只有擁有完美框架的程序才有可能成為一個非常出色的程序。
???????總的而言,在涉及臨界資源訪問時,設計開發人員需要問一下自己:訪問時這個資源是否為臨界資源?這個資源將在何種上下文中運行?資源的競爭點有哪些?對資源封裝的函數是否可以在該上下文中應用?不管怎么樣,并行程序設計時,一定要清楚,共享的資源并不是在一個上下文中引用,需要對它進行合理保護。
???????這是我對臨界資源保護的一點小結,不是很全面,都是一些個人理解,寫下文字記錄一下,希望大家批評。
總結
以上是生活随笔為你收集整理的Boost锁~临界区保护和临界资源共享的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于XML学习
- 下一篇: 腾讯宣布开放境内城市微信支付商户网络 3