• 读写锁 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_SHARED:   进程间  竞争读写锁 

    // 设置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判断是否存在写锁等待者,若有,则唤醒一个写锁等待线程
        如果没有写锁等待者,判断是否存在读锁等待者,若有,则唤醒所有的读锁等待者

  • 相关阅读:
    已经完全付款的发票仍然可以选择并进行零金额的付款
    How to fix Safari can't download .DMG
    WPF学习笔记系列
    无废话WPF系列17:数据模版
    Mac 用GUI工具打开隐藏文件
    无废话WPF系列19:MVVM简单介绍
    ASP.NET MVC3实战系列(二):面向接口编程,提高系统可测试性。
    Windows文件被占用解决办法
    无废话WPF系列18:控件模版
    ASP.NET MVC3实战系列(三):MVC3中使用依赖注入(IOC)
  • 原文地址:https://www.cnblogs.com/blackandwhite/p/12447522.html
Copyright © 2020-2023  润新知