muduo網路庫學習筆記(4):互斥量和條件變數

li27z發表於2016-08-11

事實上,muduo中對互斥量和條件變數的操作基本上都是呼叫它們對應的相關函式來實現的。例如MutexLock::lock()即呼叫pthread_mutex_lock(),Condition::wait()即呼叫pthread_cond_wait()等等。

互斥量

muduo封裝了MutexLock和MutexLockGuard。MutexLock封裝臨界區,它是一個簡單的資源類,用RAII手法封裝互斥量的建立與銷燬,MutexLock一般是別的類的資料成員。MutexLockGuard封裝臨界區的進入和退出,即加鎖和解鎖,它一般是個棧上物件,作用域剛好等於臨界區域。

類圖:
這裡寫圖片描述

(1)需要理解的是使用RAII(資源獲取就是初始化)手法封裝MutexLockGuard:不手工呼叫lock()和unlock()函式,一切交給棧上的Guard物件的構造和解構函式負責。

(2)互斥鎖通常用於保護由多個執行緒或多程式分享的共享資料,一般是一些可供執行緒間使用的全域性變數,來達到執行緒同步的目的,即保證任何時刻只有一個執行緒或程式在執行其中的程式碼。一般加鎖的輪廓如下:

pthread_mutex_lock()
臨界區
pthread_mutex_unlock()

(3)互斥量相關函式

互斥鎖的初始化有兩種初始化方式

I.對於靜態分配的互斥鎖一般用巨集賦值的方式初始化,
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

II.對於動態分配的互斥鎖(如呼叫malloc)或分配在共享記憶體中,則必須呼叫pthread_mutex_init()函式來進行初始化。

#include <pthread.h>  

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);  // 引數mutexattr指定互斥量的屬性,NULL為預設屬性

// pthread_mutex_lock和pthread_mutex_unlock都是原子操作
// 如果一個執行緒呼叫pthread_mutex_lock試圖鎖住互斥量,而該互斥量,又被其他執行緒鎖住(佔用)
// 則該執行緒的pthread_mutex_lock呼叫就會阻塞直到其他執行緒對該互斥量進行解鎖,該執行緒才能獲得該互斥量
// pthread_mutex_lock呼叫才會返回  
int pthread_mutex_lock(pthread_mutex_t *mutex);   
int pthread_mutex_unlock(pthread_mutex_t *mutex);  

int pthread_mutex_destroy(pthread_mutex_t *mutex); 

返回值:成功時返回0,失敗時返回錯誤程式碼,它們並不設定errno

(4)作者在Mutex.h程式碼的最後一行還定義了一個巨集:

// Prevent misuse like:
// MutexLockGuard(mutex_);
// A tempory object doesn't hold the lock for long!
#define MutexLockGuard(x) error "Missing guard object name"

它的作用是防止程式裡出現如下錯誤:

void doit()
{
    MutexLockGuard(mutex);  // 遺漏了變數名,產生一個臨時物件又馬上銷燬了
                            // 結果沒有鎖住臨界區
    // 正確寫法是 MutexLockGuard lock(mutex);
}

條件變數

如果需要等待某個條件成立,我們應該使用條件變數,條件變數顧名思義是一個或多個執行緒等待某個布林表示式為真,即等待別的執行緒“喚醒”它,條件變數的使用是與互斥鎖共同使用的。

muduo Condition類圖:
這裡寫圖片描述

(1)條件變數相關函式

#include <pthread.h>

// pthread_cond_t型別的變數可以用PTHREAD_COND_INITIALIZER常量進行靜態初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 

// 初始化。當cond_attr為NULL時,使用預設的屬性
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 

// 喚醒等待在相應條件變數上的一個執行緒
int pthread_cond_signal(pthread_cond_t *cond); 

// 喚醒阻塞在相應條件變數上的所有執行緒
int pthread_cond_broadcast(pthread_cond_t *cond); 

// 會自動解鎖互斥量,等待條件變數被觸發
// 在呼叫pthread_cond_wait之前,應用程式必須加鎖互斥量
// pthread_cond_wait函式返回前,自動重新對互斥量加鎖
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 

// 允許執行緒就阻塞時間設定一個限制值
// 如果在abstime指定的時間內cond未觸發,互斥量mutex被重新加鎖且pthread_cond_timedwait返回錯誤ETIMEDOUT
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); 

// 銷燬一個條件變數,釋放它擁有的資源
int pthread_cond_destroy(pthread_cond_t *cond); 

(2)需要注意的是:如果一個class要包含MutexLock和Condition,要注意它們的宣告順序和初始化順序(初始化順序要與成員宣告保持一致),mutex_應先於condition_構造,並作為後者的構造引數。

(3)clock_gettime函式和struct timespec結構
Contidion::waitForSeconds()用到了clock_gettime函式設定限制時間。

#include <time.h>

// 最高精度為納秒
struct timespec {
    time_t tv_sec;  // seconds 
    long tv_nsec;   // nanoseconds 
};

// clock_gettime函式原型
// 可以用於計算精度和納秒
long sys_clock_gettime (clockid_t which_clock, struct timespec *tp);

which_clock引數解釋:
1.CLOCK_REALTIME:系統實時時間,隨系統實時時間改變而改變,即從UTC1970-1-1 00:00:00開始計時,中間時刻如果系統時間被使用者該成其他,則對應的時間相應改變
2.CLOCK_MONOTONIC:從系統啟動這一刻起開始計時,不受系統時間被使用者改變的影響
3.CLOCK_PROCESS_CPUTIME_ID:本程式到當前程式碼系統CPU花費的時間
4.CLOCK_THREAD_CPUTIME_ID:本執行緒到當前程式碼系統CPU花費的時間

利用條件變數實現的倒數計時門閂類

類圖:
這裡寫圖片描述

倒數計時是一種常用且易用的同步手段,它主要有兩種用途:
(1)主執行緒發起多個子執行緒,等這些子執行緒各自都完成一定的任務之後,主執行緒才繼續執行。通常用於主執行緒等待多個子執行緒完成初始化。
(2)主執行緒發起多個子執行緒,子執行緒都等待主執行緒,主執行緒完成其他一些任務之後通知所有子執行緒開始執行。通常用於多個子執行緒等待主執行緒發出“起跑”命令。

CountDownLatch的介面和實現很簡單:

檔名:CountDownLatch.h

class CountDownLatch : boost::noncopyable
{
public:
  explicit CountDownLatch(int count);  // count表示倒數幾次
  void wait();  // 等待計數值變為0
  void countDown();  // 計數減一
  int getCount() const;

private:
  mutable MutexLock mutex_;
  Condition condition_;
  int count_;
};
檔名:CountDownLatch.cc

void CountDownLatch::wait()
{
    MutexLockGuard lock(mutex_);
    while(count_ > 0)
        condition_.wait();
}

void CountDownLatch::countDown()
{
    MutexLockGuard lock(mutex_);
    --count_;
    if(count_ == 0)
        conditon_.notifyAll();
}

相關文章