13

Suppose we have a class with a std::mutex:

class Foo
{
    std::mutex mutex_;
    std::string str_;
    // other members etc
public:
    friend void swap(Foo& lhs, Foo& rhs) noexcept;
}

What is the appropriate way to implement the swap method here? Is it required/safe to lock each mutex separately and then swap everything? e.g.

void swap(Foo& lhs, Foo& rhs) noexcept
{
    using std::swap;
    std::lock_guard<std::mutex> lock_lhs {lhs.mutex_}, lock_rhs {rhs.mutex_};
    swap(ls.str_, rhs.str_);
    // swap everything else
}

I've seen that in C++17, std::lock_guard will have a constructor taking multiple mutexes for avoiding deadlock, but I'm not sure if that's an issue here?

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Daniel
  • 8,179
  • 6
  • 31
  • 56

2 Answers2

11

You can use std::lock() to acquire the locks in a non-deadlocking way.

If you want to use std::lock_guard, have them adopt the locks once taken:

std::lock(lhs.mutex_, rhs.mutex_);
std::lock_guard<std::mutex> lock_a(lhs.mutex_, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.mutex_, std::adopt_lock);
//swap actions
swap(ls.str_, rhs.str_);

If you prefer std::unique_lock, then construct them without locking, then call std::lock() to lock them both (this also works with std::lock_guard):

std::unique_lock<std::mutex> lock_a(lhs.mutex_, std::defer_lock);
std::unique_lock<std::mutex> lock_b(rhs.mutex_, std::defer_lock);
std::lock(lock_a, lock_b);
//swap actions
swap(ls.str_, rhs.str_);

In both cases, you should first test for lhs and rhs being the same object, because using std::lock with one mutex twice is undefined behavior:

if (&lhs == &rhs)
    return;
Toby Speight
  • 27,591
  • 48
  • 66
  • 103
malchemist
  • 434
  • 2
  • 13
2

I don't think your swap implementation is safe. If another algorithm tries to lock rhs.mutex_ first and then lhs.mutex_, you may end up with a deadlock. Try std::lock() instead.

mpromonet
  • 11,326
  • 43
  • 62
  • 91
Lingxi
  • 14,579
  • 2
  • 37
  • 93