面试题答案
一键面试可能出现的问题
- 竞争条件:当多个线程同时尝试访问和修改
GlobalObject
的状态时,可能会导致数据不一致。例如,线程 A 和线程 B 同时进入GlobalObject
的析构函数延迟调用逻辑,可能会对析构函数中的资源释放操作进行重复执行或者部分执行,导致程序行为不可预测。 - 内存泄漏:如果没有正确的同步机制,可能会出现一个线程已经释放了资源,而另一个线程仍然认为资源可用并尝试访问,这可能导致悬空指针,进而在后续代码中引发内存泄漏。
同步机制解决方案
- 互斥锁(Mutex):互斥锁可以保证在同一时间只有一个线程能够访问
GlobalObject
的相关操作,从而避免竞争条件。 - 条件变量(Condition Variable):条件变量可以用于线程间的同步,当某个条件满足时,唤醒等待的线程。在这种情况下,可以用于等待
GlobalObject
的资源被正确释放。
代码示例
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class GlobalObject {
public:
~GlobalObject() {
std::cout << "GlobalObject destructor: releasing resources." << std::endl;
// 模拟资源释放操作
}
};
GlobalObject globalObject;
std::mutex globalMutex;
std::condition_variable globalCV;
bool globalObjectDestroyed = false;
void threadFunctionA() {
std::unique_lock<std::mutex> lock(globalMutex);
// 模拟一些操作
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!globalObjectDestroyed) {
globalObject.~GlobalObject();
globalObjectDestroyed = true;
globalCV.notify_all();
} else {
// 等待资源释放完成
globalCV.wait(lock, [] { return globalObjectDestroyed; });
}
// 后续操作
std::cout << "Thread A finished." << std::endl;
}
void threadFunctionB() {
std::unique_lock<std::mutex> lock(globalMutex);
// 模拟一些操作
std::this_thread::sleep_for(std::chrono::seconds(1));
if (!globalObjectDestroyed) {
globalObject.~GlobalObject();
globalObjectDestroyed = true;
globalCV.notify_all();
} else {
// 等待资源释放完成
globalCV.wait(lock, [] { return globalObjectDestroyed; });
}
// 后续操作
std::cout << "Thread B finished." << std::endl;
}
int main() {
std::thread threadA(threadFunctionA);
std::thread threadB(threadFunctionB);
threadA.join();
threadB.join();
return 0;
}
代码分析
GlobalObject
类:定义了全局对象GlobalObject
,其析构函数负责资源释放。- 全局变量:
globalObject
:全局对象实例。globalMutex
:互斥锁,用于保护对globalObject
的访问。globalCV
:条件变量,用于线程间同步。globalObjectDestroyed
:标志位,用于判断globalObject
是否已经被析构。
threadFunctionA
和threadFunctionB
:- 每个线程首先获取
globalMutex
的锁。 - 检查
globalObjectDestroyed
标志位,如果为false
,则调用globalObject
的析构函数并设置标志位为true
,然后通知所有等待的线程。 - 如果标志位为
true
,则等待条件变量,直到globalObject
被正确析构。
- 每个线程首先获取
main
函数:创建并启动两个线程threadA
和threadB
,然后等待它们完成。
通过这种方式,使用互斥锁和条件变量有效地避免了多线程环境下可能出现的竞争条件和内存泄漏问题。