面试题答案
一键面试挑战分析
- 资源释放顺序问题:在多线程环境下,不同线程可能同时访问和操作RAII对象。如果资源释放顺序不当,可能导致数据竞争或悬空指针等问题。例如,一个线程释放了某个资源,而另一个线程还在尝试访问该资源。
- 死锁风险:当多个RAII对象之间存在相互依赖关系时,不同线程获取这些对象的顺序不一致可能导致死锁。比如线程A获取对象A,然后尝试获取对象B;而线程B获取对象B,然后尝试获取对象A,就可能产生死锁。
解决方案及优缺点
方案一:使用锁(Mutex)
代码示例:
#include <iostream>
#include <thread>
#include <mutex>
class Resource {
public:
Resource() { std::cout << "Resource acquired" << std::endl; }
~Resource() { std::cout << "Resource released" << std::endl; }
};
class RAIIWithMutex {
public:
RAIIWithMutex() : res(std::make_unique<Resource>()) {}
~RAIIWithMutex() = default;
void doWork() {
std::lock_guard<std::mutex> lock(mutex);
// 访问资源的操作
std::cout << "Doing work with resource" << std::endl;
}
private:
std::unique_ptr<Resource> res;
std::mutex mutex;
};
void threadFunction(RAIIWithMutex& raiiObj) {
raiiObj.doWork();
}
int main() {
RAIIWithMutex raii;
std::thread t1(threadFunction, std::ref(raii));
std::thread t2(threadFunction, std::ref(raii));
t1.join();
t2.join();
return 0;
}
优点:
- 实现相对简单,能够有效避免数据竞争问题。通过锁机制,同一时间只有一个线程可以访问受保护的资源,确保资源释放顺序的正确性。 缺点:
- 性能开销,每次访问资源都需要获取和释放锁,这会增加额外的时间开销,在高并发场景下可能成为性能瓶颈。
- 可能导致死锁,如果锁的使用不当,例如锁的获取顺序不一致,仍然可能引发死锁问题。
方案二:使用线程本地存储(TLS)
代码示例:
#include <iostream>
#include <thread>
#include <memory>
thread_local std::unique_ptr<Resource> localResource;
class Resource {
public:
Resource() { std::cout << "Resource acquired in thread " << std::this_thread::get_id() << std::endl; }
~Resource() { std::cout << "Resource released in thread " << std::this_thread::get_id() << std::endl; }
};
void threadFunction() {
if (!localResource) {
localResource = std::make_unique<Resource>();
}
// 使用本地资源的操作
std::cout << "Using local resource in thread " << std::this_thread::get_id() << std::endl;
}
int main() {
std::thread t1(threadFunction);
std::thread t2(threadFunction);
t1.join();
t2.join();
return 0;
}
优点:
- 避免了多线程间对资源的竞争,每个线程都有自己独立的资源副本,不存在资源释放顺序和死锁问题。性能上相对较好,因为不需要锁的竞争。 缺点:
- 资源利用率低,每个线程都有一份资源副本,会占用更多的内存空间。不适用于需要共享资源的场景,如果某些操作需要跨线程共享数据,这种方式就无法满足需求。