日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

muduo学习笔记 - 第2章 线程同步精要

發布時間:2024/4/18 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 muduo学习笔记 - 第2章 线程同步精要 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第2章 線程同步精要

2.1 互斥器 (mutex)

互斥器保護了臨界區,任何時刻最多只能有一個線程在mutex劃出的臨界區內活動

推薦使用原則:

  • 用RAII手法封裝mutex的創建、銷毀、加鎖、解鎖四個操作
  • 只是用非遞歸的mutex (不可重入的mutex)
  • 不手動調用lock()和unlock()函數,交給棧上的Guard對象的構造和析構負責
  • 使用Guard對象時考慮調用棧上持有的鎖,防止加鎖順序不同導致死鎖

非遞歸的mutex

mutex分為遞歸和非遞歸,也稱為可重入和非可重入。區別在于:同一個線程可以重復對recursive mutex加鎖,不能重復對non-recursive mutex加鎖。

在同一線程中對non-recursive mutex重復加鎖會立刻導致死鎖,可以幫助在編碼階段發現問題。

recursive mutex可能會隱藏一些問題,當你以為拿到一個鎖就能修改對象,沒想到外層代碼已經拿到了鎖,增在修改或讀取同一個對象

MutexLock mutex; std::vector<Foo> foos;void post(const Foo& f) { // 加鎖修改對象MutexLockGuard lock(mutex);foos.push_back(f); }void traverse() { // 加鎖訪問對象MutexLockGuard lock(mutex); for (std::vector<Foo>::const_iterator it = foos.begin();it != foos.end(); ++it) {it->doit();} }

如果Foo::doit()間接調用post,Mutex為非遞歸就會發生死鎖,Mutex為遞歸可能會導致vector的迭代器失效,程序偶爾crash

解決上面問題有兩種做法:

  • 把修改推遲,記錄循環中試圖添加修改的元素,等到循環結束再調用post
  • 如果一個函數可能在加鎖的情況被調用,也可能在為假鎖的情況被調用,可以把函數拆成兩部分
    • 跟原函數同名,函數加鎖,調用第二個函數
    • 函數名加后綴WithLockHold,不加鎖,把原來的函數體搬過來
void post(const Foo& f) {MutexLockGuard lock(mutex);postWithLockHold(f); // 編譯器自動內聯 } void postWithLockHold(const Foo& f) {foos.push_back(f); }

如上也會造成兩個問題:

  • 誤用加鎖版本,死鎖
  • 無用不加鎖版本,數據損壞
  • 對于(1)可以通過調用棧進行排錯,對于(2)可以在調用的時候判斷鎖是否時調用線程加的 (isLockedByThisThread)

    2.2 條件變量

    • 條件變量和mutex一起使用, 布爾表達式受mutex保護

    • 將布爾條件判斷和wait放到while循環中

      muduo::MutexLock mutex; muduo::Condition cond(mutex); std::deque<int> queue;int dequeue() { // 出隊MutexLockGuard lock(mutex);while (queue.empty()) { // 用循環先判斷在waitcond.wait(); // 原子操作,unlock mutex進入等待,不與enqueue競爭鎖// wait()執行完重新加鎖} }void enqueue(int x) {MutexLockGuard lock(mutex);queue.push_back(x);cond.notify(); }

      上面代碼必須用while循環等待條件變量, 不能用if語句, 可能存在spurious wakeup

    • broadcast通常用于表明狀態變化, signal通常用于表示資源可用

    • 條件變量是底層的同步原語,通常用來實現高層的同步措施

      • BlockingQueue

      • DountDownLatch (倒計時)

        • 主線程發起多個子線程,等待子線程各自完成一定任務后, 主線程繼續執行, 通常用于主線程等待多個子線程完成初始化
        • 主線程發起多個子線程, 子線程等待主線程, 主線程完成一定任務后通知所有的子線程開始執行,通常用于多個子線程等待主線程發出“起跑”命令
        class CountDownLatch : boost::noncopyable{ public:explicit CountDownLatch(int count); // 倒數幾次void wait(); // 等待計數器變為0void countDown(); // 計數器減1 private:muteable MutexLock mutex_;Condition condition_;int count_; };void CountDownLatch:: wait() {MutexLockGuard lock(mutex_);while (count_ > 0) condition_.wait(); }void Count DownLatch:: countDown() {MutexLockGuard lock(mutex_);--count_;if (count == 0) condition_.notifyAll(); }

    2.3 不要用讀寫鎖和信號量

    對寫鎖

    • 典型錯誤,在持有read lock時候修改共享數據。不小心在read lock保護的函數中調用了會修改狀態的函數。

    • read lock加鎖的開銷不比mutex lock小,每次要更新reader的個數

    • reader lock的可重入可能造成死鎖

    • 線程1加讀鎖,進行讀操作

    • 線程2加寫鎖, 等待線程1,阻塞后面的讀操作

    • 線程1內部間接調用對操作,因為reader lock的可重入,線程1的讀操作阻塞

    • 發生死鎖

    信號量

    作者不建議用互斥量

    對于哲學家就餐問題,在教科書的解決方案是平權,每個哲學家有自己的線程,自己去拿筷子。作者認為集權的方式,用一個線程專門負責餐具的分配,讓其他哲學家拿著號等在食堂門口(condition variable),這樣不損失多少效率同時簡化程序

    2.4 小結

    • 線程同步盡量用高層同步設施(線程池、隊列、倒計時)
    • 讓一個正確的程序變快,遠比“讓一個快的程序變正確”容易的多
    • 真正影響性能的不是鎖,而是鎖爭用(lock contention)
    • sleep不是同步原語,盡量少使用,可以采用喚醒、輪詢、timer的方式
    • 使用shared_ptr,讀操作增加引用計數,寫的時候用shared_ptr::unique()判斷是否有其他用戶在讀。如果沒有用戶讀直接修改,如果有可以拷貝一份,在副本上修改。使用shared_ptr::swap()更新指針

    總結

    以上是生活随笔為你收集整理的muduo学习笔记 - 第2章 线程同步精要的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。