使用 C++ 封裝互斥量、條件變量

本文使用 C++ RAII 機制來封裝互斥量、條件變量,使其自動管理互斥量、條件變量的生命週期,避免手動維護帶來的資源泄露等各類問題。本文使用的是 Linux 下 Pthread 庫。c++

互斥量

MutexLock

首先封裝 mutex,下面爲實現:segmentfault

class MutexLock : noncopyable {
public:
    MutexLock() {
        assert(pthread_mutex_init(&mutex_, nullptr) == 0);
    }

    ~MutexLock() {
        assert(pthread_mutex_destroy(&mutex_) == 0);
    }

    /**
     * 加鎖(僅供 MutexLockGuard 調用,嚴禁用戶調用)
     */
    void lock() {
        pthread_mutex_lock(&mutex_);
    }

    /**
     * 解鎖(僅供 MutexLockGuard 調用,嚴禁用戶調用)
     */
    void unlock() {
        pthread_mutex_unlock(&mutex_);
    }

    /**
     * 獲取互斥量原始指針(僅供 Condition 調用,嚴禁用戶調用)
     * 僅供 Condition 調用
     * @return 互斥量原始指針
     */
    pthread_mutex_t* getPthreadMutexPtr() {
        return &mutex_;
    }
private:
    pthread_mutex_t mutex_{}; // 互斥量
};

如今MutexLock對象已經能夠自動管理 mutex 了,它在構造函數中初始化 mutex,在析構函數中銷燬 mutex。多線程

MutexLockGuard

MutexLock 對象有一點不足的是,通常加鎖和解鎖是成對出現的,可是使用 MutexLock 對象的時候,須要本身手動進行加鎖和解鎖。這個問題比較容易解決,引入一個 MutexLockGuard 對象來管理加鎖和解鎖便可。實現以下:函數

class MutexLockGuard : noncopyable {
public:
    explicit MutexLockGuard(MutexLock &mutex) : mutex_(mutex) {
            mutex_.lock();
    }

    ~MutexLockGuard() {
        mutex_.unlock();
    }

private:
    MutexLock &mutex_;
};

MutexLockGuard 對象使用也比較簡單。線程

{
    // mutex 爲 MutexLock 對象
    MutexLockGuard lock(mutex);
    ...
}

MutexLockGuard 對象建立的時候,加鎖。當 MutexLockGuard 對象離開做用域時,MutexLockGuard 對象執行析構函數,解鎖。有了 MutexLockGuard 對象,就不須要手動進行加鎖和解鎖了。指針

MutexLockGuard 對象還有一個小問題:MutexLockGuard 的臨時對象能不能達到咱們想要的自動加鎖和解鎖的效果呢?code

{
    // mutex 爲 MutexLock 對象
    MutexLockGuard(mutex);
    // MutexLockGuard 對象已被銷燬
    ...
}

答案是能自動加鎖和解鎖,但不能保護臨界區。臨時對象在建立後會當即被銷燬,並非在離開做用域的時候被銷燬,因此臨時對象必不能達到鎖住資源的效果。咱們能夠定義一個宏來阻止臨時對象的使用。對象

#define MutexLockGuard(x) error "Missing guard object name"

這樣就完成了對互斥量的封裝了。繼承

條件變量

有了前面的互斥量的封裝,分裝條件變量就簡單多了。直接看代碼:生命週期

class Condition : noncopyable {
public:
    /**
     * 構造函數
     * @param mutex 互斥量
     */
    explicit Condition(MutexLock &mutex) : mutex_(mutex) {
        assert(pthread_cond_init(&cond_, nullptr) == 0);
    }

    ~Condition() {
        assert(pthread_cond_destroy(&cond_) == 0);
    }

    void wait() {
        pthread_cond_wait(&cond_, mutex_.getPthreadMutexPtr());
    }

    /**
     * 等待規定時間
     * @param second 等待的時間
     * @return 若是超時,則返回true;不然,返回false
     */
    bool waitForSecond(int second) {
        struct timespec timeout{};
        clock_getres(CLOCK_REALTIME, &timeout);
        timeout.tv_sec += second;
        return pthread_cond_timedwait(&cond_, mutex_.getPthreadMutexPtr(), &timeout) == ETIMEDOUT;
    }

    void notify() {
        pthread_cond_signal(&cond_);
    }

    void notifyAll() {
        pthread_cond_broadcast(&cond_);
    }

private:
    MutexLock &mutex_;
    pthread_cond_t cond_{};
};

由於條件變量要配合互斥量使用,須要獲取互斥量的指針,因此 MutexLock 對象提供了獲取互斥量指針的 getPthreadMutexPtr 成員函數。 C++ RAII機制不提倡傳遞裸指針,由於這樣作會有很大的風險。正如《Effective C++》第三版條款 15 中說到,這個世界並不完美,不少 APIs 直接涉及資源,例如 Unix 系統 API。因此說,這是無奈之舉。

其餘

若是你留意的話,應該注意到前面的三個對象都繼承了 noncopyable 對象,由於他們都是對象語義的,是不可拷貝的。可參考C++ noncopyable類。該實現只是簡單的實現,還有不少問題沒有考慮(例如,未考慮多線程的狀況),徹底沒有達到工業強度。

相關文章
相關標籤/搜索