MST

星途 面试题库

面试题:C++常引用在复杂数据结构与多线程环境下的考量

假设你正在开发一个使用复杂数据结构(如自定义的图结构)的多线程C++应用程序,在这种场景下,使用常引用需要注意哪些问题?如何确保常引用在多线程环境下的线程安全性?请详细分析并给出相应的代码示例及同步机制的实现思路。
14.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

使用常引用在多线程复杂数据结构场景下的注意问题

  1. 数据结构的可变性:虽然常引用本身不能修改所引用的对象,但如果数据结构内部存在可变状态(例如通过mutable关键字修饰的成员变量),则仍可能发生意外修改。
  2. 生命周期管理:确保被常引用的对象在整个常引用使用期间保持有效,否则可能导致悬空引用,在多线程环境下更难排查。
  3. 缓存一致性:多线程访问共享数据结构时,不同线程的缓存可能不一致,导致读取到陈旧数据。

确保常引用在多线程环境下线程安全性的方法

  1. 互斥锁(Mutex):使用互斥锁来保护对共享数据结构的访问。在访问常引用所指向的对象前锁定互斥锁,访问结束后解锁。
  2. 读写锁(Read - Write Lock):如果读操作远多于写操作,可以使用读写锁。多个线程可以同时进行读操作(共享访问),但写操作时需要独占访问。

代码示例

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

// 自定义图结构节点
struct GraphNode {
    int value;
    std::vector<GraphNode*> neighbors;
    GraphNode(int val) : value(val) {}
};

// 自定义图结构
class Graph {
public:
    std::vector<GraphNode*> nodes;
    std::mutex graphMutex;

    void addNode(GraphNode* node) {
        std::lock_guard<std::mutex> lock(graphMutex);
        nodes.push_back(node);
    }

    const GraphNode& getNode(int index) const {
        std::lock_guard<std::mutex> lock(graphMutex);
        return *nodes[index];
    }
};

// 线程函数
void threadFunction(Graph& graph, int index) {
    const GraphNode& node = graph.getNode(index);
    std::cout << "Thread " << std::this_thread::get_id() << " accessed node with value: " << node.value << std::endl;
}

int main() {
    Graph graph;
    graph.addNode(new GraphNode(1));
    graph.addNode(new GraphNode(2));

    std::vector<std::thread> threads;
    for (int i = 0; i < 2; ++i) {
        threads.emplace_back(threadFunction, std::ref(graph), i);
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

同步机制实现思路

  1. 互斥锁实现思路:在上述代码中,graphMutex是一个互斥锁。addNodegetNode函数在访问nodes成员变量前都会锁定互斥锁,这样在同一时间只有一个线程可以访问nodes,从而保证了数据的一致性和线程安全性。
  2. 读写锁实现思路:如果有更多的读操作,可以使用std::shared_mutex实现读写锁。读操作时使用std::shared_lock,写操作时使用std::unique_lock。例如:
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>

// 自定义图结构节点
struct GraphNode {
    int value;
    std::vector<GraphNode*> neighbors;
    GraphNode(int val) : value(val) {}
};

// 自定义图结构
class Graph {
public:
    std::vector<GraphNode*> nodes;
    std::shared_mutex graphMutex;

    void addNode(GraphNode* node) {
        std::unique_lock<std::shared_mutex> lock(graphMutex);
        nodes.push_back(node);
    }

    const GraphNode& getNode(int index) const {
        std::shared_lock<std::shared_mutex> lock(graphMutex);
        return *nodes[index];
    }
};

// 线程函数
void readThreadFunction(Graph& graph, int index) {
    const GraphNode& node = graph.getNode(index);
    std::cout << "Read Thread " << std::this_thread::get_id() << " accessed node with value: " << node.value << std::endl;
}

void writeThreadFunction(Graph& graph, int index, int newValue) {
    auto* node = graph.nodes[index];
    std::unique_lock<std::shared_mutex> lock(graphMutex);
    node->value = newValue;
    std::cout << "Write Thread " << std::this_thread::get_id() << " updated node with new value: " << newValue << std::endl;
}

int main() {
    Graph graph;
    graph.addNode(new GraphNode(1));
    graph.addNode(new GraphNode(2));

    std::vector<std::thread> readThreads, writeThreads;
    for (int i = 0; i < 2; ++i) {
        readThreads.emplace_back(readThreadFunction, std::ref(graph), i);
        writeThreads.emplace_back(writeThreadFunction, std::ref(graph), i, i + 10);
    }

    for (auto& thread : readThreads) {
        thread.join();
    }
    for (auto& thread : writeThreads) {
        thread.join();
    }

    return 0;
}

在这个示例中,读操作使用std::shared_lock可以共享访问graphMutex,而写操作使用std::unique_lock独占访问,确保了线程安全。