How to understand destructor of scoped_lock?Does cppreference make a mistake?

297 views Asked by At
 ~scoped_lock()
  { std::apply([](auto&... __m) { (__m.unlock(), ...); }, _M_devices); }

How to understand [](auto&... __m) { (__m.unlock(), ...);? I don't understand the ... in lambda and I don't know how this implement release mutexes in reverse order.

Just as @HolyBlackCat say,
(__m.unlock(), ...) means (__m1.unlock(),(__m2.unlock(), (__m3.unlock(), (...)))), but it does not implement unlocking in reverse order.

In cppreference.com :

When control leaves the scope in which the scoped_lock object was created, the scoped_lock is destructed and the mutexes are released, in reverse order.

I make an experiment to confirm this as follows:

#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>

class mymutex : public std::mutex {
 public:
   void lock() {
     std::mutex::lock();
     std::cout << "mutex " << _i << " locked" << std::endl;
   }
  mymutex(int i): _i(i){}
   bool try_lock() {
    bool res = std::mutex::try_lock();
    if (res) {
      std::cout << "mutex " << _i << " try locked" << std::endl;
    }
    return res;
   }
  void unlock() {
    std::mutex::unlock();
    std::cout << "mutex " << _i << " unlocked" << std::endl;
  }
 private:
  int _i;
};

class Speaking {
 private:
  int a;
  mymutex my1;
  mymutex my2;
  mymutex my3;

public:
  Speaking() : a(0), my1(1), my2(2), my3(3){};
  ~Speaking() = default;
  void speak_without_lock();
  void speak_with_three_lock();
};

void Speaking::speak_without_lock() {
  std::cout << std::this_thread::get_id() << ": " << a << std::endl;
  a++;
}

void Speaking::speak_with_three_lock() 
{
  std::scoped_lock<mymutex, mymutex, mymutex> scoped(my1, my2, my3);
  speak_without_lock();
}

int main() {
  Speaking s;

  s.speak_with_three_lock();

  return 0;
}
mutex 1 locked
mutex 2 try locked
mutex 3 try locked
1: 0
mutex 1 unlocked
mutex 2 unlocked
mutex 3 unlocked

So does cppreference make a mistake?

1

There are 1 answers

0
Michael Burr On BEST ANSWER

I believe that cppreference.com is incorrect in this detail. C++17 says:

~scoped_lock();

Effects: For all i in [0, sizeof...(MutexTypes)), get(pm).unlock()

which implies that the locks are released in the same order they were taken.

Note that to prevent deadlock, releasing locks in the reverse order of acquiring them is not necessary - it's only necessary to always acquire them in the same order.