面试题答案
一键面试使用常引用在多线程复杂数据结构场景下的注意问题
- 数据结构的可变性:虽然常引用本身不能修改所引用的对象,但如果数据结构内部存在可变状态(例如通过
mutable
关键字修饰的成员变量),则仍可能发生意外修改。 - 生命周期管理:确保被常引用的对象在整个常引用使用期间保持有效,否则可能导致悬空引用,在多线程环境下更难排查。
- 缓存一致性:多线程访问共享数据结构时,不同线程的缓存可能不一致,导致读取到陈旧数据。
确保常引用在多线程环境下线程安全性的方法
- 互斥锁(Mutex):使用互斥锁来保护对共享数据结构的访问。在访问常引用所指向的对象前锁定互斥锁,访问结束后解锁。
- 读写锁(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;
}
同步机制实现思路
- 互斥锁实现思路:在上述代码中,
graphMutex
是一个互斥锁。addNode
和getNode
函数在访问nodes
成员变量前都会锁定互斥锁,这样在同一时间只有一个线程可以访问nodes
,从而保证了数据的一致性和线程安全性。 - 读写锁实现思路:如果有更多的读操作,可以使用
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
独占访问,确保了线程安全。