面试题答案
一键面试可能遇到的问题
- 线程安全问题
- 智能指针引用计数的非原子操作:智能指针通过引用计数管理内存,在多线程环境下,如果多个线程同时访问和修改引用计数,可能导致数据竞争。例如,当一个线程尝试释放对象(减少引用计数),而另一个线程同时增加引用计数时,可能会出现不一致的情况,最终导致对象被提前释放或内存泄漏。
- 对象生命周期管理混乱:不同线程可能在不同时间访问智能指针,可能出现一个线程正在使用智能指针指向的对象,而另一个线程已经释放了该对象的情况,导致悬空指针引用,程序崩溃。
- 资源竞争问题
- 共享资源访问冲突:多个智能指针可能指向同一个共享资源,不同线程通过这些智能指针访问共享资源时,如果没有适当的同步机制,可能会导致数据不一致。例如,一个线程正在修改共享资源,另一个线程同时读取该资源,可能读到未完成修改的数据。
解决方法
- 使用原子引用计数的智能指针
- 在C++11及以后,可以使用
std::shared_ptr
和std::weak_ptr
,它们的引用计数操作是原子的,能有效避免引用计数修改时的数据竞争。 std::weak_ptr
可以解决对象生命周期管理混乱的问题,它不增加引用计数,只是观察std::shared_ptr
所管理的对象。当std::shared_ptr
所管理的对象被释放时,std::weak_ptr
会自动失效,通过lock()
方法可以获取一个有效的std::shared_ptr
,如果对象已被释放,lock()
将返回一个空的std::shared_ptr
。
- 在C++11及以后,可以使用
- 同步机制
- 互斥锁(Mutex):可以使用
std::mutex
来保护对共享资源的访问。在访问共享资源前锁定互斥锁,访问完成后解锁,这样可以确保同一时间只有一个线程能访问共享资源。 - 读写锁(Read - Write Lock):如果共享资源的读操作远多于写操作,可以使用读写锁(如
std::shared_mutex
)。多个线程可以同时进行读操作,但写操作时需要独占锁,以保证数据一致性。
- 互斥锁(Mutex):可以使用
代码示例及解释
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
class SharedResource {
public:
void doWork() {
std::cout << "Doing work in SharedResource" << std::endl;
}
};
std::shared_ptr<SharedResource> sharedPtr;
std::mutex sharedPtrMutex;
void threadFunction() {
std::unique_lock<std::mutex> lock(sharedPtrMutex);
if (!sharedPtr) {
sharedPtr = std::make_shared<SharedResource>();
}
lock.unlock();
// 这里可以在解锁后使用sharedPtr,因为已经确保了对象的创建
sharedPtr->doWork();
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
解释:
- 互斥锁保护智能指针操作:在
threadFunction
中,通过std::unique_lock<std::mutex>
锁定sharedPtrMutex
,确保在创建或访问sharedPtr
时,同一时间只有一个线程能操作,避免资源竞争。这里在检查并创建sharedPtr
后解锁,是因为后续对sharedPtr
的使用不会改变其状态,所以不需要一直持有锁,提高并发性能。 std::shared_ptr
的原子引用计数:代码中使用std::shared_ptr
管理SharedResource
对象,其引用计数操作是原子的,避免了引用计数修改时的线程安全问题。即使多个线程同时创建或销毁指向SharedResource
的std::shared_ptr
,也不会导致引用计数混乱。