C++封裝互斥量和條件變量

互斥量

(1)互斥量是保護臨界區的另一種方法,當執行線程在臨界區的執行時間很長時,那麼就最好使用互斥量了,否則會造成其他的線程將會在臨界區外忙等,浪費CPU時間;此時其他線程發現臨界區已經被互斥量鎖住,那麼它們將會阻塞;當互斥量被釋放時,有多個線程在阻塞,多個線程均會被喚醒,但是隻有一個線程可以獲得該鎖,其他的線程將會繼續阻塞;

(2)當執行線程需要在臨界區睡眠時,那麼就最好使用互斥量,如果採用自旋鎖,那麼其他的線程將會在臨界區外忙等,浪費CPU時間;

(3)Posix的互斥量支持遞歸加鎖和非遞歸加鎖,對於非遞歸加鎖可能會造成死鎖,試想如果一個已經持有某互斥量的線程繼續想要持有該鎖,由於不支持遞歸,因此程序將會死鎖(自己將自己鎖死),進而我們可能需要修改程序的邏輯;而遞歸加鎖,雖然可以讓該線程繼續執行,但是會使得臨界區的數據被破壞,造成程序也有可能會崩潰

互斥量的實現

(C++封裝Linux的pthread系統調用)

class Mutex final  
{  
public:  
  Mutex(const Mutex&) = delete;  
  Mutex& operator=(const Mutex&) = delete;  
  
  explicit Mutex(bool processShared = false) :  
    _processShared(processShared)  
  {  
    pthread_mutexattr_t attr;  
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);  
  
    if (processShared) {  
        int shared;  
        pthread_mutexattr_getpshared(&attr, &shared);  
        assert(shared ==  PTHREAD_PROCESS_PRIVATE);  
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);  
    }  
     
    int err = pthread_mutex_init(&_mutex, &attr);  
    (void) err;  
  }  
  
  ~Mutex()  
  {  
    int err = pthread_mutex_destroy(&_mutex);  
    (void) err;  
  }  
  
  int lock()  
  {  
    return pthread_mutex_lock(&_mutex);  
  }  
  
  int unlock()  
  {  
    return pthread_mutex_unlock(&_mutex);  
  }  
  
  pthread_mutex_t* getMutexPtr()  
  {  
    return &_mutex;  
  }  
  
private:  
  pthread_mutex_t _mutex;  
  bool _processShared;  
};  
說明幾點:

(1)_processShared參數爲是否支持跨進程的互斥量,默認爲單進程的false;互斥量的屬性爲PTHREAD_MUTEX_NORMAL,即不允許遞歸加鎖;

(2)pthread_mutex_t* getMutexPtr()是爲了條件變量而實現的,下文介紹;

(3)利用C++中構造函數和析構函數來初始化和銷燬一個互斥量;

互斥量的使用

  1. class MutexLockGuard final  
    {  
    public:  
      MutexLockGuard(const MutexLockGuard&) = delete;  
      MutexLockGuard& operator=(const MutexLockGuard&) = delete;  
      
      explicit MutexLockGuard(Mutex& mutex) :  
          _mutex(mutex)  
      {  
        _mutex.lock();  
      }  
      
      ~MutexLockGuard()  
      {  
        _mutex.unlock();  
      }  
      
    private:  
      Mutex& _mutex;  
    };  

說明:

MutexLockGuard中持有該_mutex;利用C++中構造函數和析構函數來申請和釋放一個互斥量;



條件變量

(1)當在臨界區中,需要等待某個條件成立時,我們應該使用條件變量,在如下代碼片段1中,如果_count 大於0時,我們需要等待該條件,即需要_cond.wait();該_cond.wait()過程是將會把調用線程放到等待條件的線程列表上,然後對該互斥量解鎖;此時在互斥量解鎖期間,又有新的線程進入該臨界區,條件尚未發生,_cond.wait()會繼續這一過程;

(2)在代碼片段2中,首先會進行條件檢查(已經被同一個互斥量鎖主,睡眠的線程不可能錯過),如果_count==0  _cond.wakeAll()將會喚醒線程,記住需要在條件變化後再喚醒線程;

(3)首先_cond.wait()需要在_mutex已經上鎖的情況下才能調用,因爲_cond.wait()涉及到解鎖的過程;

(4)需要使用while (_count > 0),而不是 if (_count > 0),原因爲當線程從_cond.wait()喚醒時,此時互斥量會繼續被鎖住(此時多個線程對互斥量爭用的問題),很有可能此時的條件會被其他線程修改,造成_count > 0的條件不成立,因此需要繼續判斷的;

(5)多次執行_cond.wakeAll()發送信號時,如果沒有任何線程阻塞在該等待條件列表上,那麼這個信號會丟失,但是不影響程序;

代碼片段1:

MutexLockGuard lock(_mutex);  
while (_count > 0)    //impotant  
  {  
    _cond.wait();  
  }  
代碼片段2:
{  
  MutexLockGuard lock(_mutex);  
  --_count;  
}   
if (_count == 0)  
  {  
    _cond.wakeAll();  
  }  

條件變量的實現:
class Condition final  
{  
public:  
  Condition(const Condition&) = delete;  
  Condition& operator=(const Condition&) = delete;  
  
  Condition(Mutex& mutex) :  
      _mutex(mutex)  
  {  
    int err = pthread_cond_init(&_cond, NULL);  
    (void) err;  
  }  
  
  ~Condition()  
  {  
    int err = pthread_cond_destroy(&_cond);  
    (void) err;  
  }  
  int wait()  
  {  
    return pthread_cond_wait(&_cond, _mutex.getMutexPtr());;  
  }  
  int waitForSeconds(size_t seconds)  
  {  
    struct timespec ts;  
    clock_gettime(CLOCK_REALTIME, &ts);  
  
    ts.tv_sec += seconds;  
    return pthread_cond_timedwait(&_cond, _mutex.getMutexPtr(), &ts);  
  }  
  int wake()  
  {  
    return pthread_cond_signal(&_cond);  
  }  
  int wakeAll()  
  {  
    return pthread_cond_broadcast(&_cond);  
  }  
  
private:  
  Mutex& _mutex;  
  pthread_cond_t _cond;  
};  

說明幾點:

(1)wake爲喚醒至少一個線程;而wakeAll爲喚醒所有的線程;waitForSeconds(size_t seconds)爲等待seconds秒後,條件還未出現,那麼線程將會重新獲得互斥量(此時多個線程對互斥量爭用的問題);

(2)wait()的實現需要使用_mutex.getMutexPtr()中pthread_mutex_t類型的_mutex;








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章