读写锁 pthread_rwlock

一. 什么是读写锁

   很多时候,对共享变量的访问有以下特点:大多数情况下线程只是读取共享变量的值,并不修改,只有极少数情况下,

线程才会真正地修改共享变量的值。对于这种情况,读请求之间之间是无需同步的,他们之间的并发访问是安全的。但是

必须互斥写请求和其他读请求。
  这种情况在实际中是存在的,比如配置项。大多数时间内,配置是不会发生变化的,偶尔会出现修改配置的情况。如果

使用互斥量,完全阻止读请求并发,则会造成性能的损失。处于这种考虑,POSIX引入了读写锁。

 

二. 读写锁API

  1. 读写锁属性

    读写锁属性(pthread_rwlockattr_t)有两种: lockkind和pshared。

    (1)lockkind:读写策略,包括读取优先(默认属性)、写入优先。

    读取优先:如果在写锁请求后面到来的读锁请求不被写锁请求阻塞。如果读锁请求前仆后继源源不断地到来,只要有

        一个读锁没完成,写锁就没分。 该策略会导致较早到的写锁饿死

    写入优先:一旦线程申请写锁,在写锁请求后面到来的读锁请求就会统统被阻塞,不能先于写请求拿到锁。

enum
{
PTHREAD_RWLOCK_PREFER_READER_NP, //读者优先(默认属性)
PTHREAD_RWLOCK_PREFER_WRITER_NP, //读者优先
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, //写者优先
PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP //读者优先
};
/* 获取与设置属性 */
int pthread_rwlockattr_getkind_np(const pthread_rwlockattr_t * attr, int * pref);
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t * attr, int * pref);

 

     (2)pshared 

    PTHREAD_PROCESS_PRIVATE: 进程内 竞争读写锁 -- 默认属性

    PTHREAD_PROCESS_PUBLIC:   进程间  竞争读写锁 

// 设置pshared属性
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
// 获取pshared属性
int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *attr, int *pshared);

 

  1. 创建

int pthread_rwlock_init(pthread_rwlock_t *rwlock,  const pthread_rwlockattr_t *attr);

pthread_rwlock_t  rwlock = PTHREAD_RWLOCK_INITIALIZER

  2. 获取

int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr)  //读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr)  //写锁

  3. 释放

int pthread_rwlock_unlock(pthread_rwlock_t *rwptr)  //将读锁或写锁解锁

  4. 销毁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

 

三. 读写锁实现原理

变量  说明
__lock 管理读写锁全局竞争的锁,无论是读锁写锁还是解锁,都会互斥
__writer 写锁持有者的线程ID,如果为0则表示当前无线程持有写锁
__nr_reads 读锁持有线程的个数
__nr_reads_queued 读锁的派对等待线程的个数
__nr_writers_queued 写锁的排队等待线程的个数

  无论是申请读锁还是申请写锁,还是解锁,都至少会做一次全局互斥锁(对应__lock)的加锁和解锁,若不考虑阻塞,单单考虑

操作本身的开销,读写锁的加解锁开销是互斥锁的两倍。当然,函数结束前或进入阻塞之前,会将全局的互斥锁释放。

1. 对于读锁请求而言,如果:

    (I) 无线程持有写锁,即__writer==0

    (II) 采用的是读者优先策略或没有写锁的等待者(__nr_writers_queued==0)

  当满足这两个条件时,读锁请求都可以立即获得读锁,返回之前执行__nr_readers++,表示增加了一个线程占有读锁。

  不满足的话,则执行__nr_readers_queued++,表示增加一个读锁等待者,然后调用futex,陷入阻塞。醒来之后,会执行

__nr_readers_queued–,然后再次判断是否同时满足条件1和2

2. 对于写锁请求而言,如果:

    (I) 无线程持有写锁,即__writer==0

    (II) 没有线程持有读锁,即__nr_readers==0

  只要满足上述条件,就会立刻拿到写锁,将__writer置为线程的ID(调度域)

  如果不满足,那么执行__nr_writers_queued++,表示增加一个写锁等待者线程,然后执行mutex陷入等待。醒来后,先执行

__nr_writers_queued–,然后重新判断条件1和2。

3. 对于解锁而言,如果当前锁是写锁,则执行如下操作:

    执行__writer=0,表示释放写锁

    根据__nr_writers_queued判断有没有写锁等待者,如果有则唤醒一个写锁等待者

 如果没有写锁等待者,则判断有没有读锁等待者;如果有,则将所有的读锁等待者一起唤醒。

 如果当前锁是读锁,则执行如下操作:

    执行__nr_readers–,表示读锁占有者少了一个

    判断__nr_readers是否等于0,是的话则表示自己是最后一个读锁占有者,需要唤醒写锁等待者或者读锁等待者:

    根据__nr_writers_queued判断是否存在写锁等待者,若有,则唤醒一个写锁等待线程
    如果没有写锁等待者,判断是否存在读锁等待者,若有,则唤醒所有的读锁等待者

上一篇:2019年8月15日星期四(系统编程)


下一篇:linux与线程