11-读写锁

2020/07/03 cpp_concurrency 共 2212 字,约 7 分钟

读写锁可以分为:公平锁,读优先,写优先,优先级锁等。Linux系统提供了pthread_rwlock系列函数作为读写锁的实现,同样的Boost库提供了share_lock作为读写锁实现的辅助类。C++标准库没有提供读写锁,但是我们可以实用mutex和condition_variable来很容易的实现读写锁。


  • boost::shared_lock
  • std::unique_lock
  • std::lock_guard
  • pthread_rwlock_init
  • pthread_rwlock_destroy
  • pthread_rwlock_rdlock
  • pthread_rwlock_wrlock
  • pthread_rwlock_unlock

读写锁实现思路

  • 公平锁:实用队列来管理所,先到先得
  • 读优先:这种场合用于写少读多的情况,只要有读请求则写请求永远等待
  • 写优先:这种场合和读优先相反,只要有写请求,则读永远被阻塞
  • 优先级锁:带有优先级的锁,优先级高的锁先获取资源,可以使用set管理请求资源的锁,并按照优先级排序

公平锁

其实,标准库提供的mutex就是一种公平锁,因为被唤醒的线程是随机的。如果强调真正意义的公平,则可以使用队列来管理锁,只有处于队列头的锁才能获取资源。其实Linux系统已经实现了公平锁——在pthread_mutex初始化时传入参数mutexattr,其包含如下几种:

  • PTHREAD_MUTEX_TIMED_NP ,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
  • PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
  • PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
  • PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。

因此,只需要在创建mutex的时候指定 PTHREAD_MUTEX_TIMED_NP 属性即可。

读优先/写优先

读优先和写优先的本质都是一样的。在Linux中有线程的读写锁,同样的也可以指定读写锁的属性。

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); attr 共有 3 种选择

  • PTHREAD_RWLOCK_PREFER_READER_NP (默认设置) 读者优先,可能会导致写者饥饿情况
  • PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和 PTHREAD_RWLOCK_PREFER_READER_NP 一致
  • PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁

这里用C++的mutex和condition_variable实现一个写优先的锁。满足以下逻辑:

  • 当读锁占用锁的时候,其他线程也可以获取读锁
  • 当写锁占用锁的时候,其他任何线程既不可以获取读锁,也不可以获取写锁
  • 当有写锁在等待的时候,优先唤醒写锁
class write_priotity_lock
{
public:
    void read_lock()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_read_cv.wait(lock,[this](){
            return this->m_write_count == 0;
        });

        m_read_count++;
    }

    void write_lock()
    {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_write_count++;
        m_write_cv.wait(lock,[this](){
            return this->m_write_count <= 1 && this->m_read_count == 0;
        });
    }

    void read_release()
    {
        --m_read_count;
        if(m_read_count == 0 && m_write_count > 0)
        {
            m_write_cv.notify_one();
        }
    }

    void write_release()
    {
        --m_write_count;
        if(m_write_count >= 1)
        {
            m_write_cv.notify_one();
        }
        else
        {
            m_read_cv.notify_all();
        }
    }

private:
    std::condition_variable m_write_cv;
    std::condition_variable m_read_cv;
    std::atomic<int32_t> m_read_count;
    std::atomic<int32_t> m_write_count;
    std::mutex m_mutex;
};

文档信息

Search

    Table of Contents