WhatABeautifulWorld
WhatABeautifulWorld

Reputation: 3388

Copy ctor with mutex as data member

For classes with mutex as members, when I create the copy constructor, I need to decide which mutex to lock. For the following code, I am wondering why I only need to lock rhs.mu_ but don't have to lock this->mu_? Could it be possible that the copy constructor being called by multiple threads for the same object?

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

Updates: Is this piece of code calling the copy ctor at the same time using new?

Obj* t = nullptr;
Obj someObj;

// ... populate someObj

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

Upvotes: 0

Views: 722

Answers (2)

Matteo Italia
Matteo Italia

Reputation: 126877

You'll need to lock the mutex of the copied-from object (to make sure you're copying an object in a consistent state), while you can leave alone the lock of the object being constructed.

This is true in general for constructors: a constructor call is single-thread by definition:

  • local variables are local to the current thread (as the stack is per-thread); an instance of them is constructed whenever a particular thread enters the relevant scope;
  • thread local variables are thread local (duh), the construction of each thread local instance happens in the relevant thread;
  • static storage duration variables (globals, static locals, static class fields) are guaranteed to be initialized once by the standard (the compiler injects something similar to call_once);
  • objects allocated through "regular" new are, again, safe, as each thread running an expression like new A will allocate a different instance over which the constructor will run.

The only problem you can have is if some client of yours plays with placement new, but in that case I'd argue that it's the caller's responsibility not to call placement new constructor concurrently over the same object - as:

  • even in the non concurrent case, calling placement new twice over the same memory location (without a destructor call in between) is a contract violation on your caller's part, so serializing through a mutex in your constructor would not solve anything;
  • besides, a mutex instance member wouldn't solve anything, as you'd have a race condition anyway on the initialization of the mutex itself. To make it work you would need a global mutex, and you'd still not solve the point above.

So, long story short, don't worry about locking the object being constructed in construction. The language guarantees you that object construction is nonconcurrent.

Upvotes: 3

llllllllll
llllllllll

Reputation: 16424

If your variable is local, it's unaccessible to other threads during construction, because other thread can't name a local variable in this thread.

If your variable has static lifetime, thread safety is guaranteed by C++ standard:

[stmt.dcl]

Dynamic initialization of a block-scope variable with static storage duration or thread storage duration is performed the first time control passes through its declaration;

...

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

Thus the constructor won't be called twice.

Upvotes: 4

Related Questions