zoukankan      html  css  js  c++  java
  • 一步一步实现读写锁

    多线程编程中,需要对共享变量进行加锁。但是频繁地加锁,会对程序效率有很大影响。在某些读多写少的场景下,多个线程进行读数据时,如果都加互斥锁,这显然是不必须的。于是读写锁便应运而生。

    读写锁的加锁规则:

    1 如果没有加写锁时,那么多个线程可以同时加读锁;如果有加写锁时,不可以加读锁

    2 不管是加了读锁还是写锁,都不能继续加写锁。

    满足这两个条件,便可以初步实现一个读写锁。我们用两个锁,一个变量,实现一个简单的读写锁,代码如下

    class rwlock
    {
    public:
    	rwlock(): read_cnt(0)
    	{
                    pthread_mutex_init(&read_mtx,NULL);
                    pthread_mutex_init(&write_mtx,NULL);
    	}
    
    	~ rwlock()
    	{
                    pthread_mutex_destroy(&read_mtx);
                    pthread_mutex_destroy(&write_mtx);
    	}
    
    	void readLock()
    	{
    		pthread_mutex_lock(&read_mtx);
    		if (++read_cnt == 1)
    			pthread_mutex_lock(&write_mtx);
    		pthread_mutex_unlock(&read_mtx);
    	}
    
    	void readUnlock()
    	{
    		pthread_mutex_lock(&read_mtx);
    		if (--read_cnt == 0)
    			pthread_mutex_unlock(&write_mtx);
    		pthread_mutex_unlock(&read_mtx);
    	}
    
    	void writeLock()
    	{
    		pthread_mutex_lock(&write_mtx);
    	}
    
    	void writeUnlock()
    	{
    		pthread_mutex_unlock(&write_mtx);
    	}
    
    private:
    	pthread_mutex_t read_mtx;
    	pthread_mutex_t write_mtx;
    	int read_cnt; // 读锁个数
    };
    

     首先,在加读锁时,判断读者数量,如果为1,说明自己是第一个读者,这时要加写锁。如果没有写者,加锁成功。如果有写者,那么需要等待写锁释放。

    其次,加写锁时,就是直接锁write_mtx,如果没有其他任何读者或者写者,加锁成功;否则就等待write_mtx被释放。

    这种实现方法简单明了,但是存在一个问题。当读写锁被读者占有时,这时来了写者需要等待读锁释放,如果又来了读锁却可以加锁成功。这样就可能导致,写锁很难获取,读锁一直无法释放。实际应用中,我们并不期望如此,因为这有可能导致数据不能及时更新,读取的数据是过期的。很明显,写锁的优先级应该高于读锁。那么如何实现这样的读写锁呢?

    那么在读写锁的数据结构中,应该需要两个变量,来表示在等待的读者和写者的数量。首先给出读写锁的定义

    class rwlock
    {
    public:
        rwlock();
        ~rwlock();
        void readlock();
        void writelock();
        void unlock();
        int tryreadlock();
        int trywritelock();
        
    private:
        pthread_mutex_t rwmutex;
        int refcount;   // -1表示有写者,0表示没有加锁,正数表示有多少个读者
        int readwaiters;
        int writewaiters;
        pthread_cond_t readcond;
        pthread_cond_t writecond;
    };
    

     实现如下:

    1.构造函数,负责初始化变量

    rwlock::rwlock()
    {
        refcount     = 0;
        readwaiters  = 0;
        writewaiters = 0;
        pthread_mutex_init(&rwmutex,NULL);
        pthread_cond_init(&readcond, NULL);
        pthread_cond_init(&writecond, NULL);
    }
    

     2 析构函数,销毁资源

    rwlock::~rwlock()
    {
        refcount     = 0;
        readwaiters  = 0;
        writewaiters = 0;
        pthread_mutex_destroy(&rwmutex);
        pthread_cond_destroy(&readcond);
        pthread_cond_destroy(&writecond);
    }
    

     3 加读锁

    void rwlock::readlock()
    {
        pthread_mutex_lock(&rwmutex);
        while(refcount < 0)
        {
            readwaiters++;
            pthread_cond_wait(&readcond,&rwmutex);
            readwaiters--;
        }
        refcount++;
        pthread_mutex_unlock(&rwmutex);
    }
    

     首先,对rwmutex加锁,主要是为了读区refcount变量。然后在while循环中,等待读信号量。这里要注意的是,while不能用if来判断。我们可能会认为,在readcond有信号时,说明写者已经释放了写锁,这时refcount必然会等于0,没必要用while循环。但是,请注意,pthread_cond_wait这个函数的执行过程。首先,它会释放锁rwmutex,然后等待readcond有信号,最后获得信号量时,再对rwmutex加锁。这样就会存在一种情况,在readcond获得信号之后,还没来得及对rwmutex进行加锁,另外一个线程这时来获取写锁,很显然它可以获取到,refcount变成了-1。如果不对refcount进行判断就会出错。

    4 加写锁

    void rwlock::writelock()
    {
        pthread_mutex_lock(&rwmutex);
        while(refcount != 0)
        {
            writewaiters++;
            pthread_cond_wait(&writecond,&rwmutex);
            writewaiters--;
        }
        refcount = -1;
        pthread_mutex_unlock(&rwmutex);
    }
    

     注意点和读锁一样,都是要while循环,不再重复

    5释放锁

    void rwlock::unlock()
    {
        pthread_mutex_lock(&rwmutex);
        if(refcount == -1)
            refcount = 0;
        else
            refcount--;
        if(refcount == 0)
        {
            if(writewaiters > 0)
                pthread_cond_signal(&writecond);
            else if(readwaiters > 0)
                pthread_cond_broadcast(&readcond);
        }
    
        pthread_mutex_unlock(&rwmutex);
    }
    

     解锁时,如果recount==0,说明已经没有任何人再使用读写锁,那么首先判断是否有写锁等待,如果是,置writecond有信号。如果没有写者,只有读者,那么对readcond信号量进行广播。

    到这里,读写锁的功能就介绍完了。但是注意上面的加锁接口都是阻塞的,我们接着介绍非阻塞的加锁接口

    6 非阻塞读锁

    int rwlock::tryreadlock()
    {
        int ret = 0;
        pthread_mutex_lock(&rwmutex);
        if(refcount < 0 || writewaiters > 0)
        {
            ret = -1;
        }
        else
            refcount++;
        pthread_mutex_unlock(&rwmutex);
        return ret;
    }
    

    7 非阻塞写锁

    int rwlock::trywritelock()
    {
        int ret = 0;
        pthread_mutex_lock(&rwmutex);
        if(refcount != 0 )
        {
            ret = -1;
        }
        else
            refcount = -1;
        pthread_mutex_unlock(&rwmutex);
        return ret;
    }
    

     非阻塞接口相对比阻塞接口简单,这里就不再重复讲述了。

    总结:本文详细介绍了读写锁的功能,以及实现方法。实现都是基于posix接口,适用于所有类unix系统。至于windows系统,可参考这篇博文

     http://blog.csdn.net/querw/article/details/7214925

  • 相关阅读:
    侧边栏导航(移动端商品--评论--详情)随楼层滑动高亮显示
    PHP+Mamcached分布式部署方案设计
    【转载】关于thinkphp标签最大嵌套层数的问题
    滚动条滑动到指定位置
    PHP面向对象
    KindEditor使用过程中,用JQ提交表单时,获取不到编辑器的内容
    【转载】mysql导入大批量数据出现MySQL server has gone away的解决方法
    DAO层使用mybatis框架有关实体类的有趣细节
    spring boot集成MyBatis 通用Mapper 使用总结
    12 Spring Data JPA:springDataJpa的运行原理以及基本操作(下)
  • 原文地址:https://www.cnblogs.com/myd620/p/6129112.html
Copyright © 2011-2022 走看看