【C++ 多线程:读写锁的实现及使用样例】

本文介绍了多线程编程中如何安全地访问共享变量,包括使用普通互斥锁、读写锁(如C++14的shared_lock)以及高并发场景下的读写锁实现。重点讲解了读写锁的工作原理和C++14的shared_mutex在并发控制中的应用。

0、前置知识:多线程如何访问共享变量

多线程中对共享变量的访问方式有两种:读和写。其中,写操作是独占的,独占的意思就是,当线程对这个共享变量执行写操作时,其他线程既不能读,也不能写这个变量;而读操作是非独占的,多个线程可以同时读这个共享变量。

一般情况下,多线程访问某个共享变量时,每次访问时都加上一个互斥锁,这样肯定没有问题。**这里多说一句,锁是怎么使用的?**当某个线程执行到某一行代码时,我们希望接下来的代码不被其他线程执行,那么在当前行调用 lock() 函数即可,就好比把接下来的代码放进了一个房间里,这个房间上了锁,只有当前线程执行完这段代码并从房间出来时,这把锁才被打开了。

1、读写锁为何而生

假如现在一个线程 a 只是想读一个共享变量 i,因为不确定是否会有线程去写它,所以我们还是要对它进行加锁。但是这时又有一个线程 b 试图去读共享变量 i,发现被锁定了,那么b不得不等到 a 释放了锁后才能获得锁并读取 i 的值。但是这两个读操作实质上是非独占的,所以我们期望在多个线程试图读取共享变量的时候,它们可以立刻获取因为读而加的锁,而不是需要等待前一个线程释放。当然,如果一个线程用写锁锁住了临界区,那么其他线程无论是读还是写都会发生阻塞。

读写锁的特点

v2-87e63d837a3306724d939091d8b6c45b_1440w.jpg

2、简单读写锁

class readWriteLock {
private:
    std::mutex readMtx;
    std::mutex writeMtx;
    int readCnt; // 已加读锁个数
public:
    readWriteLock() : readCnt(0) {}
    void readLock()
    {
        readMtx.lock();
        if (++readCnt == 1) {
            writeMtx.lock();  // 存在线程读操作时,写加锁(只加一次)
        }
        readMtx.unlock();
    }
    void readUnlock()
    {
        readMtx.lock();
        if (--readCnt == 0) { // 没有线程读操作时,释放写锁
            writeMtx.unlock();
        }
        readMtx.unlock();
    }
    void writeLock()
    {
        writeMtx.lock();
    }
    void writeUnlock()
    {
        writeMtx.unlock();
    }
};

代码分析:

  • 类 readWriteLock 中定义了两个互斥锁 readMtx 和 writeMtx
  • 当读写锁中的读锁被某个线程加上时,先加上读互斥锁,这样保证了其他线程不能再读了;接着,再加上写互斥锁,同时计数加上 1,这样保证了其他线程不能再写了。接着,把读互斥锁释放掉,因为要允许其他线程也能读这个共享变量。也就是说,多次读时,只需要加一把写锁,表明其他线程暂时不能执行写操作。
  • 当读写锁中的读锁被某个线程释放时,也是加上了读互斥锁,这样保证多个线程同时释放读写锁中的读锁时没有问题。当没有线程读操作时,释放写互斥锁。表明其他线程可以执行写操作了。

3、简单读写锁的使用

#include <iostream>
#include <vector>
#include <mutex>
#include <thread>

volatile int var = 10; // 保持变量 var 对内存可见性,防止编译器过度优化
readWriteLock rwLock; // 定义全局的读写锁变量

void Write() {
    rwLock.writeLock();
    var += 10;
    std::cout << "write var : " << var << std::endl;
    rwLock.writeUnlock();
}

void Read() {
    rwLock.readLock();
    std::cout << "read var : " << var << std::endl;
    rwLock.readUnlock();
}

int main() {
    std::vector<std::thread> writers;
    std::vector<std::thread> readers;
    for (int i = 0; i < 10; i++) {  // 10 个写线程
        writers.push_back(std::thread(Write));  // std::thread t 的写法报错
    }
    for (int i = 0; i < 100; i++) {   // 100 个读线程
        readers.push_back(std::thread(Read));
    }
    for (auto& t : writers) {   // 写线程启动
        t.join();
    }
    for (auto& t : readers) {   // 读线程启动
        t.join();
    }
    std::cin.get();
}

程序运行结果如下:

v2-15d0f43e102fabb1190ff75a87e6fbd8_1440w.webp

上述代码虽然实现了读写并发的问题,但是无法实现多个线程同时读。关于这个问题,我们可以看看 C++ 14 是怎么处理的。

4、C++14 shared_lock(共享锁)

#include <iostream>
#include <mutex>         //unique_lock
#include <shared_mutex>  //shared_mutex shared_lock
#include <thread>

std::mutex g_io_mtx;

class ThreadSafeCounter {
   private:
    mutable std::shared_mutex mutex_;
    size_t value_ = 0;

   public:
    ThreadSafeCounter(){};
    ~ThreadSafeCounter(){};

    size_t get() const {
        // 读者, 获取共享锁, 使用shared_lock
        std::shared_lock<std::shared_mutex> lck(mutex_);
        return value_;
    }

    size_t increment() {
        // 写者, 获取独占锁, 使用unique_lock
        std::unique_lock<std::shared_mutex> lck(mutex_);
        value_++;
        return value_;
    }

    void reset() {
        // 写者, 获取独占锁, 使用unique_lock
        std::unique_lock<std::shared_mutex> lck(mutex_);
        value_ = 0;
    }
};
ThreadSafeCounter counter;
void reader(int id) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::unique_lock<std::mutex> ulck(g_io_mtx);
    std::cout << "reader #" << id << " get value " << counter.get() << "\n";
}

void writer(int id) {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::unique_lock<std::mutex> ulck(g_io_mtx);
    std::cout << "writer #" << id << " write value " << counter.increment()
              << "\n";
}

int main() {
    std::thread rth[10];
    std::thread wth[10];
    for (int i = 0; i < 4; i++) {
        rth[i] = std::thread(reader, i + 1);
    }
    for (int i = 0; i < 6; i++) {
        wth[i] = std::thread(writer, i + 1);
    }

    for (int i = 0; i < 4; i++) {
        rth[i].join();
    }
    for (int i = 0; i < 6; i++) {
        wth[i].join();
    }
    return 0;
}

程序运行结果:

v2-83871ad2af498456beedea634f8da75f_1440w.webp

5、高并发读写锁

从上一节可以看出,使用 shared_mutex 替代 mutex 就可以实现读并发。

#include <shared_mutex>

class readWriteLock {
private:
    std::shared_mutex readMtx;
    std::mutex writeMtx;
    int readCnt; // 已加读锁个数
public:
    readWriteLock() : readCnt(0) {}
    void readLock()
    {
        readMtx.lock();
        if (++readCnt == 1) {
            writeMtx.lock();  // 存在线程读操作时,写加锁(只加一次)
        }
        readMtx.unlock();
    }
    void readUnlock()
    {
        readMtx.lock();
        if (--readCnt == 0) { // 没有线程读操作时,释放写锁
            writeMtx.unlock();
        }
        readMtx.unlock();
    }
    void writeLock()
    {
        writeMtx.lock();
    }
    void writeUnlock()
    {
        writeMtx.unlock();
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值