【中间件】brpc_基础_rwlock

文章目录

  • brpc之rwlock
    • 1 简介
    • 2 关键数据结构与状态管理
      • 2.1 状态变量 (`_state`)
      • 2.2 butex 同步
    • 3 核心方法实现
      • 3.1 读锁获取 (`lock_shared()`)
      • 3.2 读锁释放 (`unlock_shared()`)
      • 3.3 写锁获取 (`lock()`)
      • 3.4 写锁释放 (`unlock()`)
    • 4 性能优化与特性
    • 5 关键代码
      • 5.1 状态常量定义
      • 5.2 写锁获取核心逻辑
    • 6 应用场景
    • 7 潜在问题与改进
    • 8 总结

brpc之rwlock

1 简介

BRPC 的 RWLock 旨在为 bthread(用户态线程)提供高效的读写同步机制,支持多读单写,通过无锁原子操作和用户态阻塞/唤醒机制(butex)减少上下文切换开销,适应高并发场景。


2 关键数据结构与状态管理

2.1 状态变量 (_state)

  • 编码方式:使用 32 位整数(int32_t)表示锁状态:
    • 低 16 位:写者相关状态(如写锁持有计数、等待标志)。
    • 高 16 位:读者计数(最大支持 65535 个并发读)。
  • 原子操作:通过 atomic 保证状态修改的原子性。

2.2 butex 同步

  • _butex 指针:指向一个 butex 对象,用于管理等待队列,实现协程的挂起与唤醒。

3 核心方法实现

3.1 读锁获取 (lock_shared())

  1. 循环尝试:通过 CAS操作增加读者计数,若写者未持有锁且无等待,则成功。
  2. 阻塞等待:若存在写者,调用 butex_wait 挂起当前协程,等待唤醒。
    while (true) {
        int32_t s = _state.load();
        if ((s & kWriteMask) == 0) { // 无写者
            if (_state.compare_exchange_weak(s, s + kReadIncr)) {
                return; // 成功获取读锁
            }
        } else {
            butex_wait(_butex, s); // 挂起协程
        }
    }
    

3.2 读锁释放 (unlock_shared())

  1. 减少读者计数:原子递减高 16 位。
  2. 唤醒写者:若读者计数归零且存在等待的写者,调用 butex_wake
    int32_t s = _state.fetch_sub(kReadIncr);
    if ((s & kReadMask) == kReadIncr && (s & kWritePending)) {
        butex_wake(_butex); // 唤醒等待的写者
    }
    

3.3 写锁获取 (lock())

  1. 设置等待标志:通过 CAS 设置写者等待位 (kWritePending)。
  2. 等待读者退出:循环检查读者计数,直到归零后持有写锁。
    while (true) {
        int32_t s = _state.load();
        if ((s & kWriteMask) == 0) { // 无写者
            if (_state.compare_exchange_weak(s, s | kWriteHeld)) {
                break; // 成功获取写锁
            }
        } else {
            butex_wait(_butex, s); // 挂起协程
        }
    }
    

3.4 写锁释放 (unlock())

  1. 清除写者状态:原子清除写锁标志。
  2. 唤醒所有等待者:通过 butex_wake_all 唤醒所有读/写协程。
    _state.fetch_and(~kWriteHeld);
    butex_wake_all(_butex);
    

4 性能优化与特性

  • 无锁原子操作:通过 CAS 减少锁争用,提升高并发性能。
  • 用户态挂起:利用 butex 挂起 bthread 而非 OS 线程,避免内核态切换。
  • 写者优先策略:写者等待标志 (kWritePending) 确保后续读者需等待写者完成,减少写饥饿。
  • 批量唤醒:写锁释放时唤醒所有等待者,快速响应新请求。

5 关键代码

5.1 状态常量定义

// rwlock.h
constexpr int32_t kReadIncr = 0x10000;      // 读者计数增量(高16位)
constexpr int32_t kWriteHeld = 0x1;         // 写锁持有标志(低1位)
constexpr int32_t kWritePending = 0x2;      // 写者等待标志(低2位)
constexpr int32_t kReadMask = 0xFFFF0000;   // 读者计数掩码
constexpr int32_t kWriteMask = 0x0000FFFF;  // 写者状态掩码

5.2 写锁获取核心逻辑

// rwlock.cpp
void RWLock::lock() {
    while (true) {
        int32_t s = _state.load(butil::memory_order_relaxed);
        if ((s & kWriteMask) == 0) { // 当前无写者
            int32_t new_s = s | kWriteHeld;
            if (_state.compare_exchange_weak(s, new_s)) {
                break; // 成功获取写锁
            }
        } else {
            // 设置写者等待标志并挂起
            if ((s & kWritePending) == 0) {
                _state.fetch_or(kWritePending);
            }
            butex_wait(_butex, s);
        }
    }
}

6 应用场景

  • 配置热更新:高频读取配置,低频更新时使用写锁保证原子性。
  • 缓存同步:多线程并发读缓存,缓存失效时写锁重建数据。
  • 资源池管理:并发申请资源(读锁),扩容/缩容时写锁修改池结构。

7 潜在问题与改进

  • 写者饥饿:若持续有读者进入,写者可能长时间等待,可通过限制最大读者数优化。
  • 优先级反转:高优先级协程等待低优先级协程释放写锁,需结合优先级继承机制。
  • 调试复杂性:无锁代码难以调试,需依赖详尽的单元测试和日志。

8 总结

BRPC 的 RWLock 实现通过原子状态管理和 butex 用户态同步机制,为 bthread 提供了高效的读写锁功能。其核心优势在于:

  • 高并发读:无锁原子操作最大化读性能。
  • 低延迟唤醒:用户态协程挂起/唤醒避免内核开销。
  • 写者优先:通过等待标志减少写者饥饿。

开发者需结合场景权衡读者/写者比例,必要时调整策略(如引入公平队列)以优化公平性。

你可能感兴趣的:(中间件,C/C++,中间件)