C++ 记一次结构体中使用互斥(std::mutex) 引发的程序异常

本文讲述了作者在C++后台服务中遇到的接收不到请求的异常,通过排查发现可能是由于并发情况下map遍历与clear操作导致的死锁。通过测试和分析,博主揭示了这种隐藏的死锁问题,并提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务内容

  • c++后台服务, 接口接收数据同步(每次只能可能有一个请求在执行)。

引发异常现象

  • 线上版本服务一直正常执行,突然有一天服务接收不到请求了。前端所有请求都收不到了。

解决思路

  • 第一次出现。运营说已经半个小时无法操作了。慌得一批,赶紧查看,只发现后台接收不到数据了。不管了先重启服务。重启了,又一切正常了。跟运营小姐姐说,好了但是我还不知道原因。
  • 开始怀疑是框架内网关处理的问题,其他服务有没有问题。先不管了还有很多事情没做,先做其他的。
  • 隔一天第二次出现。还是怀疑框架内网关有问题。先让框架的同事查看下,框架的同事说没有问题我先重启了保证客户能用。
  • 开始讨论。出现这种问题的原因大概率是死锁了。因为每次只有一个请求能处理,现在是所有的请求都收不到了,也就是说又一次的请求没有返回,这才会导致其他的请求无法接受。开始逐一排查死锁问题。花了一天全都查了一遍没有明显的死锁问题。想不通,下班了回去再看,郁闷。回家的路上骑着小电驴回顾一天的死锁。想到程序中有一个服务重置的机智。这个地方会clear掉所有的数据(数据结构为std::map<int,struct>,struct中有一个std::mutex),会不会出现我正在遍历map的value,同时另外一个线程正好执行了clear。这就会导致我遍历的map的mutex是一个无效对象,从而导致死锁。开心,回去测试一下。

上测试代码

  • 测试单元test.hpp
#ifndef __TEST_SOME_HPP__
#define __TEST_SOME_HPP__

#include <iostream>
#include <mutex>
#include <map>
#include "BaseFunc.h"
#include <thread>
#include <list>
using namespace std;

#define aaaa(a) #a
#define aabb(a,b) a##b

void run_TestSome()
{
    int64_t iAccID;
    cout << iAccID << endl;
}


#pragma region 测试mutex

struct StructMutex_s
{
    std::mutex _mux;
    std::map<int,int> _mapData;
    void add(int i, int j) {
        std::unique_lock<std::mutex> lck(_mux);
        _mapData[i] = j;
    }
};

std::map<int, StructMutex_s> g_mapStructMutexData;

void printStructMutexData()
{
    for (auto& item : g_mapStructMutexData)
    {
        basefunc::Sleep(600);
        std::unique_lock<std::mutex> lck(item.second._mux);
        for (auto& data : item.second._mapData) {
            cout << data.first <<";"<<data.second << endl;
        }
    }
    
}

void clearStructMutexData()
{
    basefunc::Sleep(1000);
    g_mapStructMutexData.clear();
    cout << "clear finish" << endl;
}

void run_TestStructMutex(){
    for (int i = 0; i < 10; ++i) {
        g_mapStructMutexData[i].add(i, i+1);
    }

    std::list<std::thread> listThread;
    std::thread t1(
        [&](){
            clearStructMutexData();
        });
    std::thread t2(
        [&](){
            printStructMutexData();
        });
    t1.join();
    t2.join();
}


#pragma endregion

#endif
  • main
#include "test.hpp"
int main()
{
	run_TestStructMutex();
	return 0;
}

总结

  • 死锁问题不一定是显示的lock之后没有unlock,还有可能互斥相已经不存在了。
<think>好的,我现在需要回答用户关于C++中static std::mutex的定义和用法的问题。首先,我要回忆一下std::mutex的基本概念。std::mutexC++标准库中用于多线程同步的互斥量,用于保护共享资源不被同时访问。用户提到的是static std::mutex,这意味着这个互斥量是静态的,可能属于某个类或函数内的静态成员。 接下来,我需要确认static std::mutex的定义方式。根据C++的规则,静态成员变量需要在类外进行定义和初始化。例如,在类内部声明为static std::mutex mtx;,然后在类外部定义std::mutex ClassName::mtx;。同时,C++17引入了内联变量的支持,允许在声明时直接初始化,这可能简化代码。 然后,关于用法,静态互斥量通常用于保护类的静态成员或全局共享资源。比如在多线程环境中,多个线程访问某个静态变量时,需要用互斥量进行同步。例如,在单例模式中,双重检查锁定可能会用到静态互斥量来确保线程安全。 还要注意,使用std::mutex时需要包含<mutex>头文件,并且C++11及以上版本支持。此外,互斥量的锁定和解锁通常通过RAII机制管理,比如std::lock_guard或std::unique_lock,以避免忘解锁导致的死锁问题。 用户提供的引用中提到,std::mutex是独占互斥量,不能递归使用,所以需要确保同一线程内不会重复加锁。另外,引用中的ComputationCache结构体可能是一个缓存类,但其中没有使用互斥量,因此如果在多线程环境中使用这个类,就需要添加互斥量来保护cached_x和cached_result的访问。例如,在compute函数中检查缓存时,需要加锁确保线程安全。 可能遇到的问题包括静态成员的初始化顺序问题,尤其是在不同的编译单元中。因此,需要确保静态互斥量在使用前已经被正确初始化。此外,静态成员的生命周期是整个程序运行期间,所以需要小心避免资源泄漏。 最后,生成相关问题时,需要考虑用户可能想进一步了解的内容,比如静态互斥量的初始化方法、线程安全的单例模式、RAII机制的应用,以及不同互斥量类型的区别等。</think>### C++中static std::mutex的定义与用法 #### 1. **定义方式** 在C++中,`static std::mutex`通常用于类内或全局作用域,以保护共享资源的线程安全访问。其定义需遵循以下规则: - **类内静态成员**:在类内声明为`static`,并在类外进行初始化(C++17前需要显式定义,C++17支持内联初始化)。 ```cpp class MyClass { public: static std::mutex mtx; // 类内声明 // ... }; // 类外定义(C++17前必需) std::mutex MyClass::mtx; ``` - **全局静态互斥量**:在函数或全局作用域中使用`static`关键字定义。 ```cpp static std::mutex global_mtx; // 全局静态互斥量 ``` #### 2. **使用场景** - **保护静态成员变量**:当多个线程需要访问类的静态成员时,需用互斥量同步。 - **单例模式**:实现线程安全的单例对象初始化。 - **全局资源共享**:如日志写入、缓存访问等。 #### 3. **具体用法示例** ```cpp #include <iostream> #include <mutex> #include <thread> class Counter { public: static int value; static std::mutex mtx; static void increment() { std::lock_guard<std::mutex> lock(mtx); // RAII自动加锁/解锁 value++; } }; int Counter::value = 0; std::mutex Counter::mtx; // 类外定义静态成员 int main() { std::thread t1([]() { for (int i = 0; i < 1000; ++i) Counter::increment(); }); std::thread t2([]() { for (int i = 0; i < 1000; ++i) Counter::increment(); }); t1.join(); t2.join(); std::cout << "Final value: " << Counter::value << std::endl; // 输出2000 return 0; } ``` 此示例中,`std::lock_guard`通过RAII机制确保互斥量的自动释放,避免死锁[^2]。 #### 4. **注意事项** - **初始化顺序**:静态互斥量的初始化可能受编译单元影响,需确保在使用前已构造。 - **递归锁定**:`std::mutex`不可递归锁定,如需递归特性,改用`std::recursive_mutex`[^2]。 - **超时控制**:若需超时功能,可选用`std::timed_mutex`。 #### 5. **结合用户提供的代码** 参考用户提供的`ComputationCache`结构体[^3],若需线程安全,可修改为: ```cpp struct ComputationCache { static std::mutex mtx; // 添加静态互斥量 double cached_x = 0.0; double cached_result = 0.0; double compute(double x) { std::lock_guard<std::mutex> lock(mtx); if (cached_x != x) { cached_x = x; cached_result = /* 复杂计算 */; } return cached_result; } }; // 类外定义静态成员 std::mutex ComputationCache::mtx; ``` --- 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值