如何在不同函数中使用Mutex的Lock与Unlock?解决死锁问题
我有多个按顺序执行的函数,第一个函数确定RAM中某个位置的指针,若该位置被其他线程共享则调用Mutex的lock函数;第二个函数处理指针指向的数据,之后解锁资源。但程序执行mtx.lock()时就会卡住,无法继续运行。
我正在模拟CPU和简单图形芯片,尝试锁定CPU与GPU(分属不同线程)共同访问的内存地址,以防止两者同时访问这些内存位置。目前我为虚拟内存数组中的每个地址或地址范围都设置了Mutex,但仅在CPU线程中实现了锁,GPU端尚未实现,理论上CPU首次执行lock时不会被锁。
更具体地说,CPU运行在独立线程,GPU(含显示输出)运行在主线程,我未调用thread_cpu.join()(线程初始化方式为std::thread thread_cpu(cpu.run());),不确定这是否是问题所在。
相关代码如下:
sync.h(被多个cpp文件包含)
inline std::mutex mtx;
memory.cpp(返回指针并锁定Mutex,处理完指针指向的数据后由post_process解锁)
#include "sync.h" class Memory{ private: UInt32* virtual_memory; public: Memory(){ virtual_memory = new UInt32[0x2000]; }; UInt32* get_address(UInt32 addr_virt){ mtx.lock(); return &virtual_memory[addr_virt]; } void post_process(){ mtx.unlock(); } }
实际代码更复杂,但上述代码已体现Mutex的实现方式。请问如何实现该功能并解决当前问题?
1. 全局Mutex的定义问题
当前用inline std::mutex mtx;在头文件定义全局Mutex,可能导致每个包含该头文件的编译单元生成独立的Mutex实例——这意味着CPU线程和主线程(GPU)操作的是不同的锁,完全起不到同步作用,甚至可能因为某个编译单元内的锁未解锁,导致该单元后续的lock调用卡住。
修复方式:
- 在
sync.h中声明Mutex为外部变量:
extern std::mutex mtx;
- 在单独的实现文件(比如
sync.cpp)中定义Mutex,确保整个程序只有一个实例:
#include "sync.h" std::mutex mtx;
2. 手动锁/解锁的风险
当前设计依赖调用方在get_address后必须调用post_process解锁,一旦出现函数提前返回、异常抛出等情况,Mutex会被永久锁定,后续所有lock调用都会卡住。
修复方式:使用RAII(资源获取即初始化)机制管理锁,确保自动解锁:
方案A:封装读写操作(推荐)
直接提供内存读写接口,内部用std::lock_guard自动管理锁:
#include "sync.h" #include <mutex> class Memory{ private: UInt32* virtual_memory; public: Memory(){ virtual_memory = new UInt32[0x2000]; }; UInt32 read(UInt32 addr_virt){ std::lock_guard<std::mutex> lock(mtx); return virtual_memory[addr_virt]; } void write(UInt32 addr_virt, UInt32 value){ std::lock_guard<std::mutex> lock(mtx); virtual_memory[addr_virt] = value; } };
方案B:返回带锁的对象(需直接操作指针时)
如果必须直接操作内存指针,返回std::unique_lock对象,让锁在对象销毁时自动释放:
#include "sync.h" #include <mutex> class Memory{ private: UInt32* virtual_memory; public: Memory(){ virtual_memory = new UInt32[0x2000]; }; std::unique_lock<std::mutex> get_locked_access(UInt32 addr_virt, UInt32** out_ptr){ std::unique_lock<std::mutex> lock(mtx); *out_ptr = &virtual_memory[addr_virt]; return lock; } };
调用示例:
UInt32* ptr; auto lock = memory.get_locked_access(0x100, &ptr); // 操作ptr指向的数据 // lock销毁时自动解锁,即使中途抛出异常也不会遗漏
3. 线程管理问题
虽然未调用thread_cpu.join()不是lock卡住的直接原因,但主线程结束时若子线程(CPU线程)仍在运行,会导致程序异常终止,行为不可控。必须在主线程合适的位置(比如程序退出前)调用thread_cpu.join(),等待CPU线程执行完毕。
4. 死锁排查步骤
- 检查CPU线程中是否存在调用
get_address后未调用post_process的场景(比如分支提前返回、异常),这会导致Mutex永久锁定。 - 确认主线程(GPU)是否真的没有操作该Mutex,若主线程某处意外调用了
mtx.lock()且未解锁,会导致CPU线程的lock调用卡住。
内容的提问来源于stack exchange,提问作者Math Merkl

