互斥量
(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++中構造函數和析構函數來初始化和銷燬一個互斥量;
互斥量的使用
-
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;