面试题答案
一键面试内存管理和线程安全问题分析
- 内存管理问题
- 对象生命周期管理:当类对象作为回调函数的载体时,如果对象在回调函数执行前被释放,会导致悬空指针问题。例如,在多线程环境下,一个线程创建了
ThreadSafeClass
对象并注册其成员函数作为回调,另一个线程可能在回调函数执行前意外地删除了该对象。 - 资源竞争:如果类成员函数在回调中访问和修改类的成员变量(这些变量可能是动态分配的资源,如堆上的内存、文件句柄等),不同线程同时操作可能导致资源的不一致访问,进而出现内存泄漏或数据损坏。
- 对象生命周期管理:当类对象作为回调函数的载体时,如果对象在回调函数执行前被释放,会导致悬空指针问题。例如,在多线程环境下,一个线程创建了
- 线程安全问题
- 数据竞争:多个线程同时调用
threadSafeCallback
成员函数,如果该函数访问和修改共享数据(类的成员变量),就会发生数据竞争。例如,一个线程读取成员变量,同时另一个线程修改它,可能导致读取到不一致的数据。 - 竞态条件:当
threadSafeCallback
的执行逻辑依赖于共享数据的状态,并且多个线程可以同时改变这个状态时,可能出现竞态条件。例如,一个线程根据成员变量flag
的值决定是否执行某些操作,另一个线程在该线程检查flag
后但操作执行前改变了flag
的值,可能导致程序出现错误行为。
- 数据竞争:多个线程同时调用
解决方案
- 内存管理解决方案
- 智能指针:使用智能指针(如
std::unique_ptr
或std::shared_ptr
)来管理ThreadSafeClass
对象的生命周期。std::shared_ptr
可以确保对象在所有引用都释放后自动销毁,避免悬空指针问题。 - 对象池:可以使用对象池技术,预先分配一定数量的
ThreadSafeClass
对象,回调函数从对象池中获取对象,使用完毕后归还对象池,这样可以更好地控制对象的生命周期。
- 智能指针:使用智能指针(如
- 线程安全解决方案
- 互斥锁:在
ThreadSafeClass
中使用互斥锁(如std::mutex
)来保护共享数据。在threadSafeCallback
函数中,在访问和修改共享数据前锁定互斥锁,操作完成后解锁。 - 读写锁:如果共享数据的读取操作远多于写入操作,可以使用读写锁(如
std::shared_mutex
)。多个线程可以同时读取共享数据,但写入操作需要独占锁,以确保数据一致性。
- 互斥锁:在
示例代码
#include <iostream>
#include <thread>
#include <mutex>
#include <memory>
class ThreadSafeClass {
public:
ThreadSafeClass() : data(0) {}
void threadSafeCallback() {
std::lock_guard<std::mutex> lock(mutex_);
// 模拟一些操作
++data;
std::cout << "Callback executed, data: " << data << std::endl;
}
private:
int data;
std::mutex mutex_;
};
void workerFunction(std::shared_ptr<ThreadSafeClass> obj) {
for (int i = 0; i < 5; ++i) {
obj->threadSafeCallback();
}
}
int main() {
std::shared_ptr<ThreadSafeClass> obj = std::make_shared<ThreadSafeClass>();
std::thread t1(workerFunction, obj);
std::thread t2(workerFunction, obj);
t1.join();
t2.join();
return 0;
}
在上述代码中:
- 使用
std::shared_ptr
管理ThreadSafeClass
对象的生命周期,确保对象在所有线程使用完毕后正确销毁。 - 在
ThreadSafeClass
中使用std::mutex
和std::lock_guard
来保证threadSafeCallback
函数中对共享数据data
的线程安全访问。多个线程可以同时调用threadSafeCallback
,但对data
的修改是线程安全的。