假设我在构造函数中初始化了一个成员变量向量,并且在其他几个成员函数中读取此向量(不写入其他任何地方)。我是否需要保护对向量的访问(包括在构造函数中),还是保证在将对象用于其他线程之前将对象完全初始化并刷新到主内存?
让我举个例子:
class A
{
public:
A();
void f1();
void f2();
private:
std::vector<int> v;
};
A::A()
{
// do some setup work in v
v.push_back(1);
}
// called from thread1
void A::f1()
{
// some readonly work on v
for (auto i : v) {
// do something on i
}
}
// called from thread2
void A::f2()
{
// more readonly work on v
if (v.empty()) {
// do other work
}
}
我是否需要在A::A()
,A::f1()
和A::f2()
锁定保护v?
答案 0 :(得分:4)
一个对象是由一个线程创建的,因此在构造函数中触及成员变量时运行代码时,您永远不必担心线程安全。但是,如果在构造函数中使用静态变量,则可能需要在访问周围添加某种形式的锁定。
有一个边缘情况,构造函数中的代码可以被多个线程调用,这就是你使用byte buffer[100];
Foo *foo = new (buffer) Foo;
时的情况。例如,假设你在某处有一个缓冲区,并且你要在其中分配一个对象:
new
这里,除非你锁定user table
的调用,否则两个或多个构造函数可能并行运行,因为它们是针对同一块内存运行的。然而,这是一个真正的专业边缘情况,需要特殊处理(例如锁定放置新建筑)。
答案 1 :(得分:2)
对象由单个线程构成。 其他线程只能通过实例引用访问对象。 换句话说,在其他线程调用方法之前,对象的构造函数将完成其工作。 因此,您不需要在构造函数中实现线程安全的代码。
当然,如果另一个对象作为参数传递给构造函数,那么在构造函数中对该对象的最终访问应该是线程安全的。
答案 2 :(得分:1)
如其他答案中所述,在构造函数中实现同步原语没有意义,但如果不进行外部同步,那么并不意味着您不能参加比赛 :
std::atomic<A*> g_ptr = nullptr;
void threadFun1() {
g_ptr.store(new A{}, std::memory_order_relaxed);
}
void threadFun2() {
A* l_ptr = nullptr;
while (l_ptr == nullptr) {
l_ptr = g_ptr.load(std::memory_order_relaxed);
}
l_ptr->f1();
}
在上面的代码中,您在A
和f1
的构造函数之间存在数据争用。问题是 - 没有同步 - 从thread2的角度来看,g_ptr可能在完全构造对象之前编写。
但是,在构造函数中没有可以阻止这种竞争。相反,您必须使用外部同步方法,例如在设置全局变量后使用非宽松内存排序进行原子加载和存储操作或从thread1中启动thread2。
答案 3 :(得分:-1)
请参考下面的代码示例:
<强> model.h 强>
namespace Stackoverflow {
class Model {
public:
Model();
~Model();
std::vector<int> *integers() const { return _integers.get(); }; // read only
private:
std::unique_ptr<std::vector<int>> _integers; // registered before constructor
};
}
<强> model.cpp 强>
Stackoverflow::Model::Model() {
_integers = std::make_unique<std::vector<int>>(); // initialized
}
Stackoverflow::Model::~Model() {
_integers.release();
}
私人会员&#34; _integers&#34;将被注册但未初始化,直到调用者调用构造函数。
Stackoverflow::Model stackoverflow;
当另一个线程想要访问此向量时,请调用getter。
auto *vector = stackoverflow.integers();
当调用者实际要求向量时,成员将被完全初始化。