面试题答案
一键面试移动构造函数在多线程操作 std::vector<MyClass>
时可能面临的问题
- 数据竞争:多个线程同时对
std::vector
进行添加或删除元素操作时,移动构造函数可能会访问或修改共享数据,这会导致数据竞争。例如,一个线程可能正在移动MyClass
对象的资源,而另一个线程可能同时修改std::vector
的结构(如添加或删除元素),这可能导致未定义行为。 - 资源同步:如果
MyClass
对象持有资源(如动态分配的内存、文件句柄等),移动构造函数负责转移这些资源。在多线程环境下,可能会出现资源被重复释放或未正确转移的情况。例如,一个线程移动了资源后,另一个线程可能再次尝试移动或释放相同的资源。
优化方案
- 使用互斥锁:通过互斥锁来保护对
std::vector
的操作以及MyClass
对象的移动操作,确保同一时间只有一个线程能进行这些操作。
#include <iostream>
#include <vector>
#include <mutex>
#include <thread>
class MyClass {
public:
int data;
MyClass(int value) : data(value) {}
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = 0;
}
};
std::vector<MyClass> sharedVector;
std::mutex vectorMutex;
void addElement(MyClass obj) {
std::lock_guard<std::mutex> lock(vectorMutex);
sharedVector.emplace_back(std::move(obj));
}
void removeElement() {
std::lock_guard<std::mutex> lock(vectorMutex);
if (!sharedVector.empty()) {
sharedVector.pop_back();
}
}
int main() {
std::thread t1(addElement, MyClass(1));
std::thread t2(removeElement);
t1.join();
t2.join();
return 0;
}
- 原子操作(如果适用):对于
MyClass
中简单的原子类型成员(如std::atomic<int>
),可以使用原子操作来避免锁竞争,提高效率。但对于复杂资源,还是需要互斥锁。
#include <iostream>
#include <vector>
#include <mutex>
#include <thread>
#include <atomic>
class MyClass {
public:
std::atomic<int> data;
MyClass(int value) : data(value) {}
MyClass(MyClass&& other) noexcept : data(other.data.exchange(0)) {}
};
std::vector<MyClass> sharedVector;
std::mutex vectorMutex;
void addElement(MyClass obj) {
std::lock_guard<std::mutex> lock(vectorMutex);
sharedVector.emplace_back(std::move(obj));
}
void removeElement() {
std::lock_guard<std::mutex> lock(vectorMutex);
if (!sharedVector.empty()) {
sharedVector.pop_back();
}
}
int main() {
std::thread t1(addElement, MyClass(1));
std::thread t2(removeElement);
t1.join();
t2.join();
return 0;
}
在上述代码中,通过 std::mutex
确保了对 std::vector
的操作和 MyClass
对象的移动操作在多线程环境下的正确性。同时,对于 MyClass
中的 std::atomic<int>
类型成员,利用原子操作避免了部分锁竞争(尽管在这个场景下整体操作仍依赖互斥锁)。这样可以保证移动操作在多线程环境下的正确性和高效性。