分享

无锁编程:c++11基于atomic实现共享读写锁(写优先)

 禁忌石 2019-05-28

2015年11月04日 18:25:52 

版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/49641691

在多线程状态下,对一个对象的读写需要加锁,基于CAS指令的原子语句可以实现高效的线程间协调。关于CAS的概念参见下面的文章:

无锁编程以及CAS

在c++11中CAS指令已经被封装成了 非常方便使用的atomic模板类, 详情参见:

atomic参考

以下代码利用atomic实现了一个读写资源锁,并且可以根据需要通过构造函数参数设置成写优先(write_first)(代码在gcc5和vs2015下编译通过):

readLock/Unlock 实现共享的读取加/解锁,线程数不限,有读取线程工作时,所有的申请写入线程都会等待 
writeLock/Unlock 实现独占的写入加/解锁,同时只允许一个线程写入,当有线程在读取时,写入线程等待,当写入线程执行时,所有的读取线程都被等待。

locck/unlock语句允许嵌套 
比如

lock.readLock();
lock.readLock();...lock.readUnlock();
lock.readUnlock();
  • 1

  • 2

  • 3

  • 4

  • 5

也允许在写入状态下嵌套读取,比如

lock.writeLock();lock.writeLock();lock.readLock();...lock.readUnlock();lock.writeUnlock();lock.writeUnlock();
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

RWLock.h

#include <cstdlib>#include <cassert>#include <atomic>#include <thread>#include "raii.h"/*
 * atomic实现读写资源锁,独占写,共享读,禁止复制构造函数和'='赋值操作符
 * WRITE_FIRST为true时为写优先模式,如果有线程等待读取(m_writeWaitCount>0)则等待,优先让写线程先获取锁
 * 允许嵌套加锁
 * readLock/Unlock 实现共享的读取加/解锁,线程数不限
 * writeLock/Unlock 实现独占的写入加/解锁,同时只允许一个线程写入,
 * 当有线程在读取时,写入线程阻塞,当写入线程执行时,所有的读取线程都被阻塞。
 */class RWLock {#define WRITE_LOCK_STATUS -1#define FREE_STATUS 0private:    /* 初始为0的线程id */
    static const  std::thread::id NULL_THEAD;    const bool WRITE_FIRST;    /* 用于判断当前是否是写线程 */
    thread::id m_write_thread_id;    /* 资源锁计数器,类型为int的原子成员变量,-1为写状态,0为自由状态,>0为共享读取状态 */
    atomic_int m_lockCount;    /* 等待写线程计数器,类型为unsigned int的原子成员变量*/
    atomic_uint m_writeWaitCount;public:    // 禁止复制构造函数
    RWLock(const RWLock&) = delete;    // 禁止对象赋值操作符
    RWLock& operator=(const RWLock&) = delete;    //RWLock& operator=(const RWLock&) volatile = delete;
    RWLock(bool writeFirst=false);;//默认为读优先模式
    virtual ~RWLock()=default;    int readLock();    int readUnlock();    int writeLock();    int writeUnlock();    // 将读取锁的申请和释放动作封装为raii对象,自动完成加锁和解锁管理
    raii read_guard()const noexcept{        return make_raii(*this,&RWLock::readUnlock,&RWLock::readLock);
    }    // 将写入锁的申请和释放动作封装为raii对象,自动完成加锁和解锁管理
    raii write_guard()noexcept{        return make_raii(*this,&RWLock::writeUnlock,&RWLock::writeLock);
    }
};
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

RWLock.cpp

RWLock::RWLock(bool writeFirst):
    WRITE_FIRST(writeFirst),
    m_write_thread_id(),
    m_lockCount(0),
    m_writeWaitCount(0){
}int RWLock::readLock() {    // ==时为独占写状态,不需要加锁
    if (this_thread::get_id() != this->m_write_thread_id) {        int count;        if (WRITE_FIRST)//写优先模式下,要检测等待写的线程数为0(m_writeWaitCount==0)
            do {                while ((count = m_lockCount) == WRITE_LOCK_STATUS || m_writeWaitCount > 0);//写锁定时等待
            } while (!m_lockCount.compare_exchange_weak(count, count + 1));        else
            do {                while ((count = m_lockCount) == WRITE_LOCK_STATUS); //写锁定时等待
            } while (!m_lockCount.compare_exchange_weak(count, count + 1));
    }    return m_lockCount;
}int RWLock::readUnlock() {    // ==时为独占写状态,不需要加锁
    if (this_thread::get_id() != this->m_write_thread_id)
            --m_lockCount;    return m_lockCount;
}int RWLock::writeLock(){    // ==时为独占写状态,避免重复加锁
    if (this_thread::get_id() != this->m_write_thread_id){
        ++m_writeWaitCount;//写等待计数器加1
        // 没有线程读取时(加锁计数器为0),置为-1加写入锁,否则等待
        for(int zero=FREE_STATUS;!this->m_lockCount.compare_exchange_weak(zero,WRITE_LOCK_STATUS);zero=FREE_STATUS);
        --m_writeWaitCount;//获取锁后,计数器减1
        m_write_thread_id=this_thread::get_id();
    }    return m_lockCount;
}int RWLock::writeUnlock(){    if(this_thread::get_id() != this->m_write_thread_id){        throw runtime_error("writeLock/Unlock mismatch");
    }
    assert(WRITE_LOCK_STATUS==m_lockCount);
    m_write_thread_id=NULL_THEAD;
    m_lockCount.store(FREE_STATUS);    return m_lockCount;
}
const std::thread::id RWLock::NULL_THEAD;
  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

  • 19

  • 20

  • 21

  • 22

  • 23

  • 24

  • 25

  • 26

  • 27

  • 28

  • 29

  • 30

  • 31

  • 32

  • 33

  • 34

  • 35

  • 36

  • 37

  • 38

  • 39

  • 40

  • 41

  • 42

  • 43

  • 44

  • 45

  • 46

  • 47

  • 48

  • 49

说明1

atomic_int,atomic_uint都是从atomic类模板中派生出来的类,对应不同的数据类型

atomic是c++11标准,在gcc编译的时候必须加入std=c++11选项才能正确编译,,vs编译至少要用vs2012,因为visual studio 2012以上才支持atomic模板

说明2

如果按照默认的类定义方法,提供复制构造函数和赋值操作符=,那么可以想见,在应用中可能会产生不可预知的问题,所以参照atomic模板的写法,加入了禁止复制构造函数和对象复制操作符=的代码,

    //禁止复制构造函数
    RWLock(const RWLock&) = delete;    //禁止对象赋值操作符
    RWLock& operator=(const RWLock&) = delete;
    RWLock& operator=(const RWLock&) volatile = delete;
  • 1

  • 2

  • 3

  • 4

  • 5

说明3

这个代码还有欠缺的地方就是没有实现超时异常中止。

说明4

read_guard,write_guard函数返回的raii类参见我的另一篇博客《C++11实现模板化(通用化)RAII机制》

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多