面试题答案
一键面试1. 多线程环境下 C++ 拷贝控制和资源管理面临的问题
- 竞争条件:多个线程同时访问和修改共享资源(如动态分配的内存、文件描述符等),导致数据不一致。例如,两个线程同时对同一个动态分配的对象进行拷贝构造或赋值操作,可能会导致内存释放两次或数据损坏。
- 死锁:线程之间相互等待对方释放资源,形成死循环。例如,线程 A 持有锁 L1 并等待锁 L2,而线程 B 持有锁 L2 并等待锁 A,此时就会发生死锁。
2. 解决方案
- 锁机制:
- 互斥锁(
std::mutex
):用于保护共享资源,同一时间只有一个线程能获取锁并访问资源。 - 读写锁(
std::shared_mutex
):允许多个线程同时进行读操作,但写操作时必须独占。适用于读多写少的场景。
- 互斥锁(
- 原子操作:对基本数据类型(如
std::atomic<int>
)的操作是原子的,不会被线程调度打断,避免竞争条件。适用于简单数据类型的无锁并发访问。
3. 代码示例
- 使用互斥锁保证资源管理的线程安全性
#include <iostream>
#include <mutex>
#include <thread>
class Resource {
public:
Resource() : data(0) {}
Resource(const Resource& other) {
std::lock_guard<std::mutex> lock(other.mutex_);
data = other.data;
}
Resource& operator=(const Resource& other) {
if (this != &other) {
std::lock_guard<std::mutex> lock(other.mutex_);
data = other.data;
}
return *this;
}
~Resource() {}
void increment() {
std::lock_guard<std::mutex> lock(mutex_);
++data;
}
int get() const {
std::lock_guard<std::mutex> lock(mutex_);
return data;
}
private:
int data;
mutable std::mutex mutex_;
};
void threadFunction(Resource& res) {
for (int i = 0; i < 1000; ++i) {
res.increment();
}
}
int main() {
Resource res;
std::thread t1(threadFunction, std::ref(res));
std::thread t2(threadFunction, std::ref(res));
t1.join();
t2.join();
std::cout << "Final value: " << res.get() << std::endl;
return 0;
}
在上述代码中,Resource
类使用 std::mutex
来保护数据成员 data
。在拷贝构造、赋值运算符和数据修改成员函数中,通过 std::lock_guard
自动管理锁的获取和释放,确保线程安全。
- 使用原子操作保证简单数据类型的线程安全性
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> atomicData(0);
void atomicIncrement() {
for (int i = 0; i < 1000; ++i) {
atomicData++;
}
}
int main() {
std::thread t1(atomicIncrement);
std::thread t2(atomicIncrement);
t1.join();
t2.join();
std::cout << "Atomic final value: " << atomicData << std::endl;
return 0;
}
此代码中,std::atomic<int>
类型的 atomicData
确保了自增操作的原子性,无需额外的锁,从而避免竞争条件。