读写锁的原理与实现

可以看看之前写的文章线程里面有关于锁和生产消费者模型的介绍

什么是读写锁

生产消费模型 VS 读写模型

对于生产者和消费者模型的概念图👇
在这里插入图片描述
那么我们读写锁源自读者写着模型,和生产者和消费者模型十分类似👇
在这里插入图片描述
两个的模型实际上是一模一样的,我们还是通过“三二一”来深入了解一下读者写者模型

  • 三种关系
    • 读者-读者:没有关系,共享公共资源
    • 读者-写者:互斥、同步,因为读的时候发生了写或写的时候来读,读取到的信息都是错误的!!
    • 写者-写者:互斥,临界资源中当有一个写者时其他所有人(不论你是读者还是写者)都进不来
  • 两者角色:写者和读者
  • 一个场所:临界区

生产者和写者
两个是一模样的,都是向临界区输入数据的

读者和消费者
消费者会从临界区中拿走数据(修改临界区) 而读者不会(可以理解为对临界区表现为只读)

读写锁的pthread库接口

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);   /* 销毁RW lock */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
      const pthread_rwlockattr_t *restrict attr);       /* 初始化RW lock */

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;   /* 直接赋值方式初始化RW lock */

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);    /* 取得读锁,进入read-mode */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); /* 尝试取得读锁,失败立即返回  */

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); /* 取得写锁,进入write-mode */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);    /* 尝试取得写锁,失败立即返回  */

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);    /* 释放读/写锁 */

这些接口与普通锁的接口类似就不一一介绍了,但是我们要明确的是不管你是读者和写者都是对一把读写锁加锁,不同身份的人通过不同的接口给读写锁加锁或解锁

  • 如果你是读者,你就用int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 这个接口加锁,解锁以此类推
  • 如果你是写者,你就用int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 这个接口加锁,解锁以此类推

读者&&写者模式

关于读写锁要知道读写锁的两个模式:

  • read_mode : 再此模式下,第一个读者取得了读写锁就进入该模式,所有写者都会被阻塞,而读者可以申请到锁,当最后一个读者释放锁,退出该模式
  • write_mode :当一个写者取得读写锁就进入该模式之,所有读者和写者均被阻塞,当申请锁的写者释放锁之后退出该模式

模拟实现读写锁

读写锁的实现的思路最重要的是,不管你的身份是读者还是写者,一定要搞清楚目前临界区域的状态!
我们把状态分为三种:

  • 读模式
  • 写模式
  • 初始状态:既不是读模式,也不是写模式。

思路1——用两个锁来实现(读者优先)

实现思路:两把锁,一把我们叫读锁、一把叫写锁。读者走读锁的门,写者走写锁的门
在这里插入图片描述

  • 读者申请锁 和 释放锁
    - 第一个读者:先申请读锁进入临界区,这时他要申请写锁(如果成功这时正式进入读模式),这样写者就就无法进入临界区达到读者写者互斥。但是读者走的时候要释放读锁,否则其他读者进不来
    - 中间的读者:除了申请写锁(如果申请写锁就会造成死锁),其他和上面一模一样
    - 最后一个读者:除了最后一步释放锁的时候要把写锁归还,其他和上面一样

  • 写者申请锁 和 释放锁:就是直接申请写锁,释放就是直接释放写锁。

思考:这样的设计有什么缺点?
这样的设计实际上是读者优先,读者只要申请到锁,写者必须等待所有读者读完,很容易造成饥饿问题!思路二就设计一个写者优先的读写锁。

模拟实现

代码仓库version1

#include <pthread.h>
// 使用两个mutex来实现读写锁
namespace sht
{
    class sht_rwlock
    {
    private:
        pthread_mutex_t m1; // 用来锁读者 目的是为了保护read_num这个临界资源
        pthread_mutex_t m2; // 用来锁写者 用来控制读者之间的互斥
        int read_num = 0;   // 记录读者的数量
        int cur_state = 0;
        // 0 : 表示没有上锁
        // 1 :当前在读模式
        // -1 :当前在写模式

    public:
        // 初始化
        int sht_rwlock_init()
        {

            pthread_mutex_init(&m1, nullptr);
            pthread_mutex_init(&m2, nullptr);
            return 0;
        }

        int sht_rwlock_destroy()
        {
            pthread_mutex_destroy(&m1);
            pthread_mutex_destroy(&m2);
        }

        int sht_rwlock_rdlock()
        {
            pthread_mutex_lock(&m1);
            read_num++;

            if (read_num == 1)
                pthread_mutex_lock(&m2);
            // 改变成读者状态必须一定要放在申请到读者锁之后!!!!!!!!
            cur_state = 1;

            // 读者之间不是互斥关系,所以要解锁,否则其他读者进不来
            pthread_mutex_unlock(&m1);
            return 0;
        }

        int sht_rwlock_tryrdlock()
        {
            int ret1 = pthread_mutex_trylock(&m1);
            if (ret1 != 0)
                return -1;

            if (read_num == 1)
            {
                int ret2 = pthread_mutex_trylock(&m2);
                if (ret2 != 0)
                    return -1;
            }
            read_num++;
            cur_state = 1;

            pthread_mutex_unlock(&m1);
            return 0;
        }

        int sht_rwlock_wrlock()
        {
            int ret = pthread_mutex_lock(&m2);
            // 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
            cur_state = -1;
            return ret;
        }

        int sht_rwlock_trywrlock()
        {
            int ret = pthread_mutex_lock(&m2);
            if (ret != 0)
                return -1;
            // 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
            cur_state = -1;
            return ret;
        }

        int sht_rwlock_unlock()
        {
            if (cur_state == -1)
            {
                // 写者还锁
                pthread_mutex_unlock(&m2);
                cur_state = 0;
            }
            else if (cur_state == 1)
            {
                // 读者还锁
                pthread_mutex_lock(&m1);

                if (read_num == 1)
                    pthread_mutex_unlock(&m2);

                read_num--;

                pthread_mutex_unlock(&m1);
            }
            else
                return -1;

            return 0;
        }
    };

} // namespace sht

思路2——两个条件变量一个锁(写者优先)

        pthread_cond_t reader_cond;  //读者队列
        pthread_cond_t writer_cond;   //写者队列
        pthread_mutex_t m;
        int reader_num; // 读者等待的数量
        int writer_num; // 写者等待的数量
        int cur_state;
        // 1 : 代表读者模式
        // 0 : 代表锁是为获取的
        // -1 :代表写者模式
  • 申请读锁的思路:
    在这里插入图片描述

  • 申请写锁的思路:

在这里插入图片描述


  • 释放锁的思路

在这里插入图片描述

模拟实现

代码仓库 version2

#include <pthread.h>

// 写者优先的锁
namespace sht
{
    class sht_rwlock
    {
    public:
        int sht_rwlock_init()
        {
            pthread_cond_init(&reader_cond, nullptr);
            pthread_cond_init(&writer_cond, nullptr);
            pthread_mutex_init(&m, nullptr);
        }

        int sht_rwlock_destroy()
        {
            pthread_cond_destroy(&reader_cond);
            pthread_cond_destroy(&writer_cond);
            pthread_mutex_destroy(&m);
        }

        int sht_rwlock_rdlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == 1)
            {
                // 当前在读模式,因为我们是写者优先所以我们看看是否有写者线程在等待
                // 如果有我们就先执行写者,让读者去排队
                // 如果没有,那么读锁就申请成功
                while (writer_num > 0)
                {
                    reader_num++;
                    pthread_cond_wait(&reader_cond, &m);
                    reader_num--;
                }
            }
            else if (cur_state == -1)
            {
                // 如果当前是写者模式,读者直接去排队
                reader_num++;
                pthread_cond_wait(&reader_cond, &m);
                reader_num--;
            }
            else
            {
                cur_state = 1;
            }

            pthread_mutex_unlock(&m);
        }

        int sht_rwlock_tryrdlock()
        {
            pthread_mutex_lock(&m);

            if (cur_state == 0 || cur_state == 1 && writer_num == 0)
            {
                pthread_mutex_unlock(&m);
                return 0;
            }
            return -1;
        }

        int sht_rwlock_wrlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == -1)
            {
                writer_num++;
                pthread_cond_wait(&writer_cond, &m);
                writer_num--;
            }
            else if (cur_state == 1)
            {
                writer_num++;
                pthread_cond_wait(&writer_cond, &m);
                writer_num--;
            }
            else
            {
                // 把状态改成写者模式
                cur_state = -1;
            }
            pthread_mutex_unlock(&m);
            return 1;
        }
        int sht_rwlock_trywrlock()
        {
            pthread_mutex_lock(&m);
            if (cur_state == -1)
            {
                pthread_mutex_unlock(&m);
                return 0;
            }
            return -1;
        }
        int sht_rwlock_unlock()
        {
            pthread_mutex_init(&m, nullptr);

            if (cur_state == 1)
            {

                if (writer_num > 0)
                {
                    cur_state = -1;
                    pthread_cond_signal(&writer_cond);
                }
                else
                {
                    if (reader_num > 0)
                    {
                        pthread_cond_signal(&reader_cond);
                    }
                    else
                        cur_state = 0; // 这一步一定不能丢,如果当前两个队列均为空时,要置为初始状态
                    // 不然线程就会卡住
                }
            }
            else if (cur_state == -1)
            {
                // 写者来还锁,如果写队列不为空 ,唤醒写队列中的写者(写者优先)
                // 如果写者队列空了,但是读者队列不为空,唤醒读者
                // 两个队列都为空,把状态置成初始状态

                if (writer_num > 0)
                {

                    pthread_cond_signal(&writer_cond);
                }
                else
                {
                    if (reader_num > 0)
                    {
                        cur_state = 1;
                        pthread_cond_signal(&reader_cond);
                    }
                    else
                        cur_state = 0;
                }
            }
            else
            {
                return -1;
            }

            pthread_mutex_unlock(&m);
        }

    private:
        pthread_cond_t reader_cond;
        pthread_cond_t writer_cond;
        pthread_mutex_t m;
        int reader_num; // 读者等待的数量
        int writer_num; // 写者等待的数量
        int cur_state;
        // 1 : 代表读者模式
        // 0 : 代表锁是为获取的
        // -1 :代表写者模式
    };
} // namespace sht

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值