简体   繁体   English

C ++构造函数线程安全

[英]C++ constructor thread safety

Let's say I have a member variable vector initialised in the constructor, and this vector is read (not written to anywhere else) in several other member functions. 假设我在构造函数中初始化了一个成员变量向量,并且在其他几个成员函数中读取了此向量(未将其写入其他位置)。 Would I need to protect access to the vector (including in the constructor), or is it guaranteed that the object will be fully initialised and flushed to main memory before it's used in other threads? 我是否需要保护对向量的访问(包括在构造函数中),还是可以保证在其他线程使用该对象之前,该对象将被完全初始化并刷新到主内存中?

Let me provide an example: 让我举一个例子:

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
    }
}

Do I need to lock-protect v in A::A() , A::f1() and A::f2() ? 我是否需要在A::A()A::f1()A::f2()锁定v?

An object is created by a single thread, so you never have to worry about thread safety when running code inside the constructor that touches member variables. 一个对象是由一个线程创建的,因此在构造函数中运行涉及成员变量的代码时,您不必担心线程安全。 However, if you are using static variables within the constructor then you may need to add some form of locking around the access. 但是,如果在构造函数中使用静态变量,则可能需要在访问周围添加某种形式的锁定。

There is an edge case where code within a constructor can be called by multiple threads, and this is when you are using either placement new . 在一个极端的情况下,构造函数中的代码可以被多个线程调用,这是在您使用placement new For example, let's say you've got a buffer somewhere, and you're going to allocate an object into it: 例如,假设您在某处有一个缓冲区,并且打算在其中分配一个对象:

byte buffer[100];
Foo *foo = new (buffer) Foo;

Here, unless you are locking around the call to new then it's possible for two or more constructors to run in parallel as they're running against the same block of memory. 在这里,除非您锁定对new的调用,否则两个或多个构造函数可能会在相同的内存块上并行运行。 However, this is a real specialized edge-case and would require special handling (eg locking around the placement-new construction). 但是,这是真正的专用边缘盒,需要特殊处理(例如,锁定新放置的结构)。

An object is constructed by a single thread. 一个对象由单个线程构造。 Other threads can access the object only by means of the instance reference. 其他线程只能通过实例引用来访问对象。 In other words, the object's constructor will have finished its work before other threads call a method. 换句话说,对象的构造函数将在其他线程调用方法之前完成工作。 You therefore don't need to implement thread-safe code within a constructor. 因此,您不需要在构造函数内实现线程安全代码。

Of course, in case another object is passed to the constructor as a parameter, eventual access to that object within the constructor, should be thread-safe. 当然,如果将另一个对象作为参数传递给构造函数,则在构造函数内对该对象的最终访问应该是线程安全的。

As stated in the other answers, there is no point in implementing synchronization primitives in the constructer, but that doesn't mean you can't have a race , if you don't synchronize externally: 如其他答案中所述,在构造器中实现同步原语没有任何意义,但这并不意味着如果您不进行外部同步, 就无法进行比赛

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();
}

In above code, you have a data race between the constructor of A and f1 . 在上面的代码中,您在Af1的构造函数之间进行了数据竞争。 The problem is that - without synchonization - from the point of view of thread2, g_ptr might be written before the object is completely constructed. 问题是-从线程2的角度来看-在没有同步的情况下,可能在对象完全构建之前编写了g_ptr。

However, there is nothing you can do inside the constructor to prevent this kind of race. 但是, 在构造函数内部您无法采取任何措施来防止这种竞争。 Instead you have to use external means of synchronization, like using non-relaxed memory ordering for the atomic load and store operations or starting thread2 from within thread1 after the global variable is set. 相反,您必须使用外部同步方式,例如对原子加载和存储操作使用非宽松的内存排序,或者在设置全局变量后从thread1内部启动thread2。

Take this code example below: 请使用以下代码示例:

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();
}

The private member "_integers" will be registered but not initialized until the constructor is being called by the caller. 私有成员“ _integers”将被注册,但不会被初始化,直到调用方调用构造函数为止。

Stackoverflow::Model stackoverflow;

When another thread want to access this vector, call the getter. 当另一个线程要访问此向量时,请调用getter。

auto *vector = stackoverflow.integers();

The member will be fully initialized when the caller is actually asking for the vector. 当调用者实际请求引导程序时,将完全初始化该成员。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM