使用 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类。该实现只是简单的实现,还有不少问题没有考虑(例如,未考虑多线程的状况),彻底没有达到工业强度。

相关文章
相关标签/搜索