面试题答案
一键面试多线程编程中使用RAII面临的挑战
- 资源竞争:多个线程可能同时访问和修改RAII对象管理的资源,导致数据不一致或未定义行为。例如,一个线程在释放资源时,另一个线程可能正在尝试使用该资源。
- 死锁:当多个RAII对象需要以不同顺序在多个线程中获取资源时,可能会形成死锁。比如线程A获取资源1并等待资源2,而线程B获取资源2并等待资源1。
通过设计合适的RAII类解决问题
- 使用互斥锁(Mutex)解决资源竞争:在RAII类中使用互斥锁来保护对资源的访问。每次访问资源前锁定互斥锁,访问结束后解锁。
#include <mutex>
#include <iostream>
class Resource {
public:
void use() {
std::cout << "Using resource" << std::endl;
}
};
class RAIIResource {
public:
RAIIResource() : res(std::make_unique<Resource>()) {}
~RAIIResource() = default;
void useResource() {
std::lock_guard<std::mutex> lock(mtx);
res->use();
}
private:
std::unique_ptr<Resource> res;
std::mutex mtx;
};
- 避免死锁的策略:
- 固定资源获取顺序:确保所有线程以相同顺序获取多个资源。例如,总是先获取资源A,再获取资源B。
- 使用锁层次结构:为资源分配层次编号,获取锁时遵循层次顺序。
- 使用
std::lock
避免死锁:std::lock
函数可以一次性锁定多个互斥锁,避免死锁。
#include <mutex>
#include <iostream>
#include <thread>
#include <vector>
class ResourceA {
public:
void use() {
std::cout << "Using ResourceA" << std::endl;
}
};
class ResourceB {
public:
void use() {
std::cout << "Using ResourceB" << std::endl;
}
};
class RAIIResourceA {
public:
RAIIResourceA() : resA(std::make_unique<ResourceA>()) {}
~RAIIResourceA() = default;
std::unique_ptr<ResourceA> resA;
std::mutex mtxA;
};
class RAIIResourceB {
public:
RAIIResourceB() : resB(std::make_unique<ResourceB>()) {}
~RAIIResourceB() = default;
std::unique_ptr<ResourceB> resB;
std::mutex mtxB;
};
void threadFunction(RAIIResourceA& ra, RAIIResourceB& rb) {
std::lock(ra.mtxA, rb.mtxB);
std::lock_guard<std::mutex> lockA(ra.mtxA, std::adopt_lock);
std::lock_guard<std::mutex> lockB(rb.mtxB, std::adopt_lock);
ra.resA->use();
rb.resB->use();
}
int main() {
RAIIResourceA ra;
RAIIResourceB rb;
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(threadFunction, std::ref(ra), std::ref(rb));
}
for (auto& th : threads) {
th.join();
}
return 0;
}
在上述代码中,std::lock
函数确保了mtxA
和mtxB
被同时锁定,避免了死锁的发生。同时,std::lock_guard
使用std::adopt_lock
来接管已经锁定的互斥锁,保证在作用域结束时正确解锁。