You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

std::shared_mutex无写锁时无法获取读锁问题排查

问题分析与解决:带日志的shared_mutex阻塞问题

代码示例

#include "shared_mutex"
#include "thread"
#include "iostream"
#include "string"

using namespace std::chrono_literals;

struct debug_mutex : public std::shared_mutex {
    void lock() noexcept {
        std::string m = "Try To lock, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                        std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m << std::endl;
        std::shared_mutex::lock();
        unique_lock_c.fetch_add(1);
        std::string m1 = "lock, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                         std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m1 << std::endl;
    }

    void unlock() noexcept {
        std::string m = "Try To unlock, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                        std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m << std::endl;
        std::shared_mutex::unlock();
        unique_lock_c.fetch_add(-1);
        std::string m1 = "unlock, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                         std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m1 << std::endl;
    }

    void lock_shared() noexcept {
        std::string m = "Try To lock_shared, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                        std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m << std::endl;
        std::shared_mutex::lock_shared();
        shared_lock_c.fetch_add(1);
        std::string m1 = "lock_shared, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                         std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m1 << std::endl;
    }

    void unlock_shared() noexcept {
        std::string m = "Try To unlock_shared, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                        std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m << std::endl;
        std::shared_mutex::unlock_shared();
        shared_lock_c.fetch_add(-1);
        std::string m1 = "unlock_shared, u_count: " + std::to_string(unique_lock_c.load()) + ", s_count" +
                         std::to_string(shared_lock_c.load()) + "\n";
        std::cout << m1 << std::endl;
    }

    std::atomic_uint64_t shared_lock_c{0};
    std::atomic_uint64_t unique_lock_c{0};
} mutex;

int main(int argc, char **argv) {
    std::thread t{[]() {
        std::this_thread::sleep_for(10ns);
        while (true) {
            mutex.lock_shared();
            mutex.lock_shared();
            mutex.lock_shared();
            mutex.lock_shared();
            mutex.lock_shared();
            mutex.lock_shared();
            std::this_thread::sleep_for(5ns);
            mutex.unlock_shared();
            mutex.unlock_shared();
            mutex.unlock_shared();
            mutex.unlock_shared();
            mutex.unlock_shared();
            mutex.unlock_shared();
            std::cout << "read lock once" << std::endl;
        }
    }};
    std::thread t1{[]() {
        std::this_thread::sleep_for(5ms);
        while (true) {
            mutex.lock();
            std::this_thread::sleep_for(5ms);
            mutex.unlock();
            std::cout << "write lock once" << std::endl;
        }
    }};
    t.join();
    t1.join();
}

问题现象

代码预期读写锁正常交替、持续运行,但实际出现异常阻塞,阻塞时日志如下:

Try To lock_shared, u_count: 0, s_count4
lock_shared, u_count: 0, s_count5
Try To lock, u_count: 0, s_count5
Try To lock_shared, u_count: 0, s_count5

问题原因

  1. std::shared_mutex的写优先特性:C++标准规定std::shared_mutex是写优先的——当有写请求在等待时,新的读请求会被阻塞,防止写请求长期饥饿。此场景中:
    • 读线程已持有多个共享锁(日志显示s_count=5),写线程发起lock()请求后进入等待队列;
    • 读线程循环中再次调用lock_shared(),此时因写请求排队,新读请求被阻塞;而写线程又因读锁未完全释放无法获取排他锁,最终形成死锁。
  2. 读线程重复加锁的不合理设计:同一读线程在未释放共享锁的情况下连续多次调用lock_shared(),虽然shared_mutex允许同一线程重复获取共享锁,但这种用法放大了写请求等待时的阻塞风险,直接触发了死锁场景。

修复方案

要让程序持续运行直至手动终止,可按以下方式调整:

1. 移除读线程的重复加锁操作

读线程只需一次共享锁即可保护整个读操作周期,无需重复加锁。调整后的读线程代码:

std::thread t{[]() {
    std::this_thread::sleep_for(10ns);
    while (true) {
        mutex.lock_shared();
        std::this_thread::sleep_for(5ns);
        mutex.unlock_shared();
        std::cout << "read lock once" << std::endl;
    }
}};

2. (可选)修复日志输出的线程安全问题

原代码中多线程直接使用std::cout会导致日志混乱,建议给输出加独立互斥锁:

#include <mutex>

std::mutex cout_mutex;

// 在所有输出日志的位置,用锁包裹:
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << m << std::endl;

3. (业务可选)自定义读优先读写锁

如果业务场景需要读优先而非标准库的写优先,可以自行实现读优先的读写锁逻辑,避免写请求排队时阻塞新读请求的情况。

内容的提问来源于stack exchange,提问作者MikuSoft

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:24:59