深度剖析:可重入锁(Recursive Lock / 递归锁)原理与实现

🔍 深度剖析:可重入锁(Recursive Lock)原理与实现


🧩 一、可重入锁核心概念
📌 1.1 问题背景

在多线程编程中,锁的嵌套调用是常见场景:

void funcA() {
    lock.lock();
    funcB(); // 内部也需获取同一把锁
    lock.unlock();
}

void funcB() {
    lock.lock();
    // ... 
    lock.unlock();
}

传统互斥锁(如std::mutex)会导致死锁——线程试图重复获取已持有的锁。

📌 1.2 可重入锁特性
  1. 同一线程可多次加锁(Reentrancy)
  2. 加锁/解锁次数必须匹配(Reentry Count)
  3. 不同线程互斥访问(Mutual Exclusion)

⚙️ 二、RecursiveSpinLock 实现剖析
📊 2.1 类结构图
RecursiveSpinLock
-SpinLock lockobj_
-std::atomic tid_
-std::atomic reentries_
+TryEnter() : noexcept
+Leave()
+Enter() : noexcept
🔄 2.2 核心工作流程

在这里插入图片描述

🔍 2.3 关键代码解析
(1) 锁获取逻辑
        template <class LockObject, class LockInternalObject, typename... TryEnterArguments>
        static constexpr bool RecursiveLock_TryEnter(LockObject&    lock, 
            LockInternalObject&                                     lock_internal, 
            std::atomic<int64_t>&                                   tid,
            std::atomic<int>&                                       reentries, 
            TryEnterArguments&&...                                  arguments)
        {
            int n = ++reentries;
            assert(n > 0);

            int64_t current_tid = GetCurrentThreadId(); /* std::hash<std::thread::id>{}(std::this_thread::get_id()); */
            if (n == 1)
            {
                bool lockTaken = lock_internal.TryEnter(std::forward<TryEnterArguments>(arguments)...);
                if (!lockTaken)
                {
                    reentries--;
                    return false;
                }

                tid.store(current_tid, std::memory_order_relaxed);
            }
            else
            {
                int64_t expected_tid = current_tid;
                if (!tid.compare_exchange_strong(expected_tid, current_tid, std::memory_order_relaxed, std::memory_order_relaxed))
                {
                    reentries--;
                    return false;
                }
            }

            return true;
        }
(2) 锁释放逻辑
        void RecursiveSpinLock::Leave()
        {
            int64_t current_tid = GetCurrentThreadId();
            int64_t expected_tid = current_tid;

            if (!tid_.compare_exchange_strong(expected_tid, current_tid, std::memory_order_relaxed, std::memory_order_relaxed))
            {
                throw std::runtime_error("Lock not owned by current thread.");
            }

            int n = --reentries_;
            if (n == 0)
            {
                tid_.store(0, std::memory_order_relaxed);
                lockobj_.Leave();
            }
            elif(n < 0)
            {
                throw std::runtime_error("Fail to release the atomic lock.");
            }
        }
(3) 死锁防御机制
~RecursiveSpinLock() noexcept(false) 
{
            if (reentries_ > 0) 
            {
                throw std::runtime_error("RecursiveSpinLock destroyed while held.");
            }
}

🌐 三、通用可重入锁实现范式
📜 3.1 标准实现模板

所有可重入锁均需三个核心组件:

  1. 底层锁(Lock Object):提供基础互斥能力
  2. 持有者标识(Owner Identifier):标记当前锁持有线程
  3. 重入计数器(Reentry Counter):记录嵌套深度

在这里插入图片描述

⚖️ 3.2 多核CPU下的关键保障
  1. 内存可见性(Memory Visibility)

    • 使用std::atomic确保跨核心数据同步
    • 严格的内存序控制:
      // 获取锁时的屏障
      _.compare_exchange_strong(..., std::memory_order_acquire)
      
      // 释放锁时的屏障
      _.compare_exchange_strong(..., std::memory_order_release)
      
  2. 操作原子性(Atomicity)

    • 重入计数修改必须原子化:
      // 非原子操作会导致计数错乱
      ++reentries_; // 正确:std::atomic<int>
      
  3. 线程局部存储(Thread-Local State)

    • 持有者ID必须与线程绑定:
      int64_t current_tid = GetCurrentThreadId();
      

🔧 四、基于 std::mutex 的可重入锁实现
🛠️ 4.1 工作原理
线程ARecursiveMutex线程Block()获取锁 (owner=A, count=1)lock() [重入]count=2 (立即返回)lock() [尝试获取]进入等待 (因owner≠id)unlock()count=1 (未完全释放)继续等待unlock()count=0, 重置ownernotify_all()获取锁 (owner=B, count=1)线程ARecursiveMutex线程B
🛠️ 4.2 代码实现
#include <mutex>
#include <condition_variable>
#include <thread>
#include <stdexcept>

class RecursiveMutex {
public:
    void lock() {
        std::thread::id this_id = std::this_thread::get_id();
        {
            std::unique_lock<std::mutex> lock(meta_mutex_);
            
            // 重入分支:当前线程已持有锁
            if (owner_ == this_id) {
                ++reentry_count_;
                return;
            }
            
            // 非持有线程等待
            while (owner_ != std::thread::id()) {
                cond_var_.wait(lock);
            }
            
            // 获取锁
            owner_ = this_id;
            reentry_count_ = 1;
        } // 自动释放meta_mutex_
    }

    void unlock() {
        std::unique_lock<std::mutex> lock(meta_mutex_);
        
        if (owner_ != std::this_thread::get_id()) {
            throw std::logic_error("非持有线程解锁");
        }
        
        if (--reentry_count_ == 0) {
            owner_ = std::thread::id(); // 重置持有者
            lock.unlock(); // 提前释放meta_mutex_
            cond_var_.notify_all(); // 唤醒所有等待线程
        }
    }

private:
    std::mutex meta_mutex_;
    std::condition_variable cond_var_;
    std::thread::id owner_;
    unsigned int reentry_count_ = 0;
};
🧩 多核CPU下的关键保障
内存可见性模型
Core1修改owner_
内存屏障
L1缓存失效
Core2看到最新值
lock-unlock操作
std::memory_order_seq_cst
全局内存顺序一致
多核心处理流程
CPU Core 2
Yes
获取meta_mutex
Thread2: unlock
reentry_count--
count==0?
notify_one
CPU Core 1
Yes
No
获取meta_mutex
Thread1: lock
owner==T1?
reentry_count++
加入等待队列
跨核心同步机制
  1. std::mutex:自带内存屏障,确保操作原子性
  2. std::condition_variable:基于futex实现,支持跨核唤醒
  3. std::thread::id:保证线程ID的跨核一致性

🧪 五、可重入锁的测试方法论
🔬 5.1 必须覆盖的测试场景
  1. 基础功能测试

    // 单线程重入
    recursive_mutex.lock();
    recursive_mutex.lock(); // 应成功
    recursive_mutex.unlock();
    recursive_mutex.unlock();
    
  2. 线程安全测试

    // 多线程竞争
    std::vector<std::thread> threads;
    for (int i=0; i<10; ++i) {
        threads.emplace_back([]{
            recursive_mutex.lock();
            // 临界区操作
            recursive_mutex.unlock();
        });
    }
    
  3. 异常场景测试

    // 过度解锁检测
    TEST(RecursiveLockTest, OverUnlock) {
        RecursiveMutex mtx;
        EXPECT_THROW({
            mtx.lock();
            mtx.unlock();
            mtx.unlock(); // 应抛出异常
        }, std::logic_error);
    }
    

🚀 六、工程实践建议
  1. 优先选择标准库

    // C++标准库已提供优化实现
    #include <mutex>
    std::recursive_mutex safe_lock;
    
  2. 避免锁嵌套

    // 重构前:两层嵌套
    void process() {
        lock.lock();
        updateCache(); // 内部有锁
        lock.unlock();
    }
    
    // 重构后:扁平化锁
    void process() {
        std::lock_guard guard(lock); 
        updateCache(); // 无锁版本
    }
    
  3. 锁粒度控制

    • 粗粒度锁:保护大块代码(简单但低效)
    • 细粒度锁:保护最小数据集(高效但复杂)
  4. 死锁检测工具

    • Clang ThreadSanitizer (-fsanitize=thread)
    • Valgrind DRD 工具

💎 总结

可重入锁的核心在于通过持有者标识+重入计数器的机制,在保持多线程互斥的前提下允许同一线程的嵌套加锁。本文剖析的两种实现方式:

  1. 自旋锁版(RecursiveSpinLock)

    • ✅ 极低延迟(纳秒级)
    • ❌ 高CPU占用(忙等待)
    • 💡 适用于短临界区
  2. 互斥锁版(RecursiveMutex)

    • ✅ CPU友好(线程休眠)
    • ❌ 较高上下文切换开销
    • 💡 适用于长临界区

在工程实践中,应优先使用std::recursive_mutex,同时通过代码重构减少锁嵌套需求,从根本上提升并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值