面试题答案
一键面试可能遇到的并发访问问题
- 数据竞争:当多个线程同时访问和修改同一个数据成员时,可能导致数据不一致。例如,线程A读取数据成员
x
,线程B同时修改x
,线程A再使用x
时,可能使用到已被修改的值,造成结果错误。 - 竞态条件:多个线程的执行顺序不确定,可能导致依赖于特定执行顺序的代码逻辑出现问题。比如初始化操作可能在使用之后才完成。
解决技术
-
锁机制
- 互斥锁(Mutex):使用互斥锁来保护对数据成员的访问。在访问和修改数据成员前,线程获取互斥锁,访问完成后释放互斥锁。这样同一时间只有一个线程能访问和修改数据成员。
- 读写锁(Read - Write Lock):适用于读多写少的场景。允许多个线程同时读数据成员,但写操作必须独占。读操作时获取读锁,写操作时获取写锁。
-
原子操作 对于一些简单的数据类型(如整数、指针等),可以使用原子操作。原子操作是不可分割的,不会被其他线程打断,从而避免数据竞争。例如,
std::atomic<int>
类型的变量,其自增、自减等操作都是原子的。 -
线程局部存储(TLS) 每个线程都有自己独立的存储空间,线程访问和修改的数据成员是自己独有的,不会与其他线程的数据产生竞争。常用于每个线程需要自己独立的状态变量等场景。
代码示例
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <thread>
#include <vector>
class MyClass {
private:
int data1;
std::atomic<int> data2;
std::mutex mtx;
public:
MyClass() : data1(0), data2(0) {}
void modifyData1(int value) {
std::lock_guard<std::mutex> lock(mtx);
data1 += value;
}
void modifyData2(int value) {
data2 += value;
}
};
void threadFunction(MyClass& obj) {
for (int i = 0; i < 1000; ++i) {
obj.modifyData1(i);
obj.modifyData2(i);
}
}
int main() {
MyClass obj;
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(threadFunction, std::ref(obj));
}
for (auto& th : threads) {
th.join();
}
std::cout << "data1: " << obj.data1 << std::endl;
std::cout << "data2: " << obj.data2 << std::endl;
return 0;
}
在上述代码中,data1
通过互斥锁mtx
来保证安全访问。modifyData1
函数在访问和修改data1
前获取互斥锁,离开作用域时自动释放。data2
使用std::atomic<int>
类型,其修改操作是原子的,无需额外锁机制。在多线程环境下,不同线程对data1
和data2
的并发访问和修改能保证数据一致性。