使用互斥锁作为数据成员复制ctor

时间:2018-02-02 05:55:14

标签: c++ mutex

对于以互斥为成员的类,当我创建复制构造函数时,我需要决定要锁定哪个互斥锁。对于以下代码,我想知道为什么我只需要锁定 rhs.mu _ 但不必锁定 this-> mu _ ?是否有可能多个线程为同一个对象调用复制构造函数?

class Obj {
 public:
  std::mutex mu_;
  std::string data_;

  // copy ctor
  Obj(const Obj& rhs) {
    std::unique_lock<std::mutex> lk(rhs.mu_); // why only lock rhs.mu_?
    data_ = rhs.data_;
  }
}

更新 这段代码是否使用 new

同时调用copy ctor
Obj* t = nullptr;
Obj someObj;

// ... populate someObj

std::thread t1([&]() { t = new Obj(someObj); });
std::thread t2([&]() { t = new Obj(someObj); });

2 个答案:

答案 0 :(得分:3)

如果你的变量是本地的,那么在构造过程中它是其他线程无法访问的,因为其他线程不能在这个线程中命名一个局部变量。

如果您的变量具有静态生命周期,则C ++标准保证线程安全:

[stmt.dcl]

  

第一次控件通过其声明时,执行具有静态存储持续时间或线程存储持续时间的块范围变量的动态初始化;

     

...

     

如果控件在初始化变量时同时进入声明,则并发执行应等待初始化完成。

因此构造函数不会被调用两次。

答案 1 :(得分:2)

您需要锁定复制对象的互斥锁(以确保您以一致的状态复制对象),而您可以单独保留正在构造的对象的锁。

对于构造函数来说,这是正确的:根据定义,构造函数调用是单线程的:

  • 局部变量是当前线程的本地变量(因为堆栈是每线程的);只要特定线程进入相关范围,就会构造它们的实例;
  • 线程局部变量是线程本地(duh),每个线程本地实例的构造都发生在相关的线程中;
  • 静态存储持续时间变量(全局,static本地,static类字段)保证按标准初始化一次(编译器注入类似于call_once的东西);
  • 通过“常规”new分配的对象再次是安全的,因为运行像new A这样的表达式的每个线程将分配一个不同的实例,构造函数将在该实例上运行。

你可以遇到的唯一问题是,如果你的某个客户使用展示位置new,但在这种情况下,我认为调用者有责任不同时调用展示位置new构造函数。相同的对象 - 如:

  • 即使在非并发的情况下,在相同的内存位置调用放置new两次(之间没有析构函数调用)是调用者的合同违规,因此在构造函数中通过互斥锁序列化不会解决任何问题;
  • 此外,互斥实例成员无法解决任何问题,因为无论如何在互斥锁本身的初始化时都会遇到竞争条件。为了使它工作,你需要一个全局互斥,你仍然没有解决上述问题。

所以,长话短说,不要担心在构造中锁定正在构造的对象。该语言保证对象构造是非并发的。