对象动态变量被mutex锁定时析构函数是否无法释放内存?分离线程场景下mutex的使用正确性咨询
先来看你给出的核心代码和测试场景:
基础对象定义
#include <thread> #include <mutex> using namespace std; mutex mu; class Object { public: char *var; Object() { var = new char[1]; var[0] = 1; } ~Object() { mu.lock(); delete[]var; // 析构函数确实需要释放自身持有的动态内存 mu.unlock(); } }*object = nullptr;
测试用的多线程代码
void do_something() { for(;;) { mu.lock(); if(object) if(object->var[0] < 255) object->var[0]++; else object->var[0] = 0; mu.unlock(); } } int main() { object = new Object(); thread th(do_something); th.detach(); Sleep(1000); delete object; object = nullptr; return 0; }
接下来逐个解答你的疑问:
1. 是否存在var无法被析构函数释放的可能?
不会。只要delete object;被执行到,Object的析构函数就一定会被调用,而析构函数里的delete[] var;也会正常释放这块动态内存。不过要注意:如果主线程delete object之后,分离线程还在尝试访问object->var,这会触发未定义行为(比如程序崩溃、内存乱码),但var本身的释放操作是能完成的。
2. 分离线程里使用mutex的方式是否正确?
逻辑方向是对的,但有个可以优化的点:手动调用lock()和unlock()容易因为代码变更或者异常(虽然这里没有异常场景)导致锁无法释放,更推荐用RAII风格的锁管理类,比如std::lock_guard,它会在构造时自动加锁,离开作用域时自动解锁,代码更简洁安全:
void do_something() { for(;;) { lock_guard<mutex> guard(mu); // 自动管理锁的生命周期 if(object) { if(object->var[0] < 255) object->var[0]++; else object->var[0] = 0; } } }
2.1 是否需要用mutex包裹delete object语句?
必须要! 你当前的代码里,主线程delete object时没有加锁,极大概率会出现竞态条件:比如分离线程刚拿到锁,进入if(object)判断为真,还没来得及访问var,主线程就把object删除并设为nullptr了,这时候分离线程再访问object->var就是在操作一个野指针,直接触发未定义行为。
正确的做法是给主线程的delete操作也加上锁:
int main() { object = new Object(); thread th(do_something); th.detach(); Sleep(1000); // 加锁保护object的销毁操作 mu.lock(); delete object; object = nullptr; mu.unlock(); return 0; }
这样就能保证,主线程销毁object的时候,分离线程要么在等待锁,要么已经完成了一轮访问并释放了锁,不会出现中间状态的非法访问。
关于Mutex的核心理解补充
你后来对Mutex的理解完全正确:它本质就是一个带有“锁定状态”的同步工具,核心作用是保证同一时间只有一个线程能进入临界区。用普通bool变量模拟的话,会因为线程切换的不确定性导致竞态条件(比如两个线程同时看到bool为false,都进入临界区),而Mutex的lock/unlock是原子操作,由操作系统底层支持,能彻底避免这种问题。
至于你最后问的「为什么多线程访问mutex本身不会导致未定义行为?」——因为Mutex的设计就是线程安全的!它的所有公开方法(lock、unlock、try_lock等)都是原子实现的,操作系统会保证多个线程对同一个Mutex对象的操作是有序且安全的,不会出现竞态条件,这也是它能用来做线程同步的核心原因。
内容的提问来源于stack exchange,提问作者Ngdgvcb

