MST
星途 面试题库

面试题:C++ RAII在多线程环境下的挑战与解决方案

在多线程编程中使用RAII会面临哪些挑战?你将如何通过设计合适的RAII类来解决这些问题,例如资源竞争和死锁等情况,请详细阐述并给出代码示例。
15.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

多线程编程中使用RAII面临的挑战

  1. 资源竞争:多个线程可能同时访问和修改RAII对象管理的资源,导致数据不一致或未定义行为。例如,一个线程在释放资源时,另一个线程可能正在尝试使用该资源。
  2. 死锁:当多个RAII对象需要以不同顺序在多个线程中获取资源时,可能会形成死锁。比如线程A获取资源1并等待资源2,而线程B获取资源2并等待资源1。

通过设计合适的RAII类解决问题

  1. 使用互斥锁(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;
};
  1. 避免死锁的策略
    • 固定资源获取顺序:确保所有线程以相同顺序获取多个资源。例如,总是先获取资源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函数确保了mtxAmtxB被同时锁定,避免了死锁的发生。同时,std::lock_guard使用std::adopt_lock来接管已经锁定的互斥锁,保证在作用域结束时正确解锁。