面试题答案
一键面试多线程环境中使用RAII技术管理共享资源面临的挑战
- 死锁:如果多个线程以不同顺序获取多个互斥锁,可能会形成死锁。例如线程A获取锁1,然后尝试获取锁2,而线程B获取锁2,然后尝试获取锁1,就会陷入死锁。
- 锁竞争:频繁获取和释放锁会导致性能下降,因为线程上下文切换开销较大。如果多个线程频繁地访问共享资源,锁竞争会成为瓶颈。
- 异常安全:在构造函数或析构函数中抛出异常时,可能导致资源管理出现问题,如锁未正确释放。
设计场景及实现
假设我们有一个共享资源 SharedResource
,多个线程需要对其进行读写操作。我们使用RAII机制来管理互斥锁 std::mutex
。
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
class SharedResource {
public:
void update(int value) {
std::lock_guard<std::mutex> lock(mutex_);
data_ = value;
std::cout << "Updated data to: " << data_ << std::endl;
}
int read() const {
std::lock_guard<std::mutex> lock(mutex_);
return data_;
}
private:
int data_ = 0;
mutable std::mutex mutex_;
};
void threadFunction(SharedResource& resource, int value) {
resource.update(value);
std::cout << "Read data: " << resource.read() << std::endl;
}
int main() {
SharedResource resource;
std::vector<std::thread> threads;
for (int i = 1; i <= 5; ++i) {
threads.emplace_back(threadFunction, std::ref(resource), i);
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
设计思路和关键技术点
- RAII机制:使用
std::lock_guard
来自动管理互斥锁的生命周期。在构造时获取锁,在析构时释放锁,确保异常安全。 - 减少锁竞争:
- 细粒度锁:如果可能,将大的共享资源划分为多个小的部分,每个部分使用单独的锁。这样不同线程可以同时访问不同部分,减少竞争。
- 读写锁:对于读多写少的场景,可以使用
std::shared_mutex
,允许多个线程同时读,但只允许一个线程写。
- 死锁预防:
- 固定顺序获取锁:如果需要获取多个锁,确保所有线程以相同顺序获取。
- 使用
std::lock
:std::lock
可以一次性获取多个锁,避免死锁。例如std::lock(mutex1, mutex2);
。
- 性能优化:
- 延迟初始化:对于不常用的共享资源,延迟初始化可以避免不必要的锁竞争。
- 锁粗化:如果一系列操作都需要锁,可以将锁的范围扩大,减少锁获取和释放的次数。