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

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

互斥量

MutexLock

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

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 的臨時對象能不能達到我們想要的自動加鎖和解鎖的效果呢?

{
    // 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類
該實現只是簡單的實現,還有很多問題沒有考慮(例如,未考慮多線程的情況),例如完全沒有達到工業強度。

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