zoukankan      html  css  js  c++  java
  • 系统程序员成长计划并发(四)(下)

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    读写锁

    读写锁在加锁时,要区分是为了读而加锁还是为了写而加锁,所以和递归锁不同的是,它无法兼容Locker接口了。不过为了做到不依赖于特定平台,我们可以利用Locker的接口来抽象锁的实现。利用现有的锁来实现读写锁。读写锁的可变的部分已经被Locker隔离了,所以读写锁本身不需要做成接口。它只是一个普通对象而已:

    struct _RwLocker;
    typedef struct _RwLocker RwLocker;

    RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker);

    Ret rw_locker_wrlock(RwLocker* thiz);
    Ret rw_locker_rdlock(RwLocker* thiz);
    Ret rw_locker_unlock(RwLocker* thiz);

    void rw_locker_destroy(RwLocker* thiz);

    o 创建读写锁

    RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker)
    {
        RwLocker* thiz = NULL;
        return_val_if_fail(rw_locker != NULL && rd_locker != NULL, NULL);

        thiz = (RwLocker*)malloc(sizeof(RwLocker));
        if(thiz != NULL)
        {
            thiz->readers = 0;
            thiz->mode = RW_LOCKER_NONE;
            thiz->rw_locker = rw_locker;
            thiz->rd_locker = rd_locker;
        }

        return thiz;
    }

    读写锁的基本要求是:写的时候不允许任何其它线程读或者写,读的时候允许其它线程读,但不允许其它线程写。所以在实现时,写的时候一定要加锁,第一个读的线程要加锁,后面其它线程读时,只是增加锁的引用计数。我们需要两个锁:一个锁用来保存被保护的对象,一个锁用来保护引用计数。

    o 加写锁

    Ret rw_locker_wrlock(RwLocker* thiz)
    {
        Ret ret = RET_OK;
        return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

        if((ret = locker_lock(thiz->rw_locker)) == RET_OK)
        {
            thiz->mode = RW_LOCKER_WR;
        }

        return ret;
    }

    加写锁很简单,直接加保护受保护对象的锁,然后修改锁的状态为已加写锁。后面其它的线程想写,就会这个锁上等待,如果想读也要等待(见后面)。

    o 加读锁

    Ret rw_locker_rdlock(RwLocker* thiz)
    {
        Ret ret = RET_OK;
        return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

        if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
        {
            thiz->readers++;
            if(thiz->readers == 1)
            {
                ret = locker_lock(thiz->rw_locker);
                thiz->mode = RW_LOCKER_RD;
            }
            locker_unlock(thiz->rd_locker);
        }

        return ret;
    }

    先尝试加保护引用计数的锁,增加引用计数。如果当前线程是第一个读,就要去加保护受保护对象的锁。如果此时已经有线程在写,就等待直到加锁成功,然后把锁的状态设置为已加读锁,最后解开保护引用计数的锁。

    o 解锁

    Ret rw_locker_unlock(RwLocker* thiz)
    {
        Ret ret = RET_OK;
        return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);

        if(thiz->mode == RW_LOCKER_WR)
        {
            thiz->mode == RW_LOCKER_NONE;
            ret = locker_unlock(thiz->rw_locker);
        }
        else
        {
            assert(thiz->mode == RW_LOCKER_RD);
            if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
            {
                thiz->readers--;
                if(thiz->readers == 0)
                {
                    thiz->mode == RW_LOCKER_NONE;
                    ret = locker_unlock(thiz->rw_locker);
                }
                locker_unlock(thiz->rd_locker);
            }
        }

        return ret;
    }

    解锁时根据状态来决定,解写读直接解保护受保护对象的锁。解读锁时,先要加锁保护引用计数的锁,引用计数减一。如果自己是最后一个读,才解保护受保护对象的锁,最后解开保护引用计数的锁。

    从上面读写锁的实现,我们可以看出,读写锁要充分发挥作用,就要基于两个假设:

    o 读写的不对称性,读的次数远远大于写的次数。像数据库就是这样,决大部分时间是在查询,而修改的情况相对少得多,所以数据库通常使用读写锁。

    o 处于临界区的时间比较长。从上面的实现来看,读写锁实际上比正常加/解锁的次数反而要多,如果处于临界区的时间比较短,比如和修改引用计数差不多,使用读写锁,即使全部是读,它的效率也会低于正常锁。

    本节示例请到这里下载。
  • 相关阅读:
    bzoj 1030: [JSOI2007]文本生成器
    hdu 2222 Keywords Search
    poj 2406 Power Strings
    poj 2752 Seek the Name, Seek the Fame
    ASP.NET中WebForms简介与搭建HelloWorld项目
    VisualStudio2017中新建项目没有Asp.Net项目
    C#中导出百万级Excel只需几秒除了NPOI还可以这样
    Winform中实现双击Dev的TreeList在ZedGraph中生成对应颜色的曲线
    Winform中设置ZedGraph在生成多条曲线时随机采用不同的颜色
    DevExpress的TreeList实现自定义节点NodeCell的背景颜色和前景色
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167568.html
Copyright © 2011-2022 走看看