Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::lock_guard/std::unique_lock use type erasure?

Tags:

Why do std::lock_guard and std::unique_lock necessitate specifying the lock type as a template parameter?

Consider the following alternative. First, in a detail namespace, there are type erasure classes (a non-template abstract base class, and a template derived class):

#include <type_traits> #include <mutex> #include <chrono> #include <iostream>  namespace detail {      struct locker_unlocker_base {         virtual void lock() = 0;         virtual void unlock() = 0;     };      template<class Mutex>     struct locker_unlocker : public locker_unlocker_base {         locker_unlocker(Mutex &m) : m_m{&m} {}         virtual void lock() { m_m->lock(); }         virtual void unlock() { m_m->unlock(); }         Mutex *m_m;     }; } 

Now te_lock_guard, the type erasure lock guard, simply placement-news an object of the correct type when constructed (without dynamic memory allocation):

class te_lock_guard { public:     template<class Mutex>     te_lock_guard(Mutex &m) {         new (&m_buf) detail::locker_unlocker<Mutex>(m);         reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->lock();     }     ~te_lock_guard() {         reinterpret_cast<detail::locker_unlocker_base *>(&m_buf)->unlock();     }  private:     std::aligned_storage<sizeof(detail::locker_unlocker<std::mutex>), alignof(detail::locker_unlocker<std::mutex>)>::type m_buf; }; 

I've checked the performance vs. the standard library's classes:

int main() {     constexpr std::size_t num{999999};     {         std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();         for(size_t i = 0; i < num; ++i) {             std::mutex m;             te_lock_guard l(m);         }         std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now();         std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl;     }     {         std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();         for(size_t i = 0; i < num; ++i) {             std::mutex m;             std::unique_lock<std::mutex> l(m);         }         std::chrono::steady_clock::time_point end= std::chrono::steady_clock::now();         std::cout << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << std::endl;     } } 

Using g++ with -O3, there is no statistically-significant performance loss.

like image 545
Ami Tavory Avatar asked Sep 09 '16 08:09

Ami Tavory


People also ask

What is the difference between unique_lock and Lock_guard?

A lock_guard always holds a lock from its construction to its destruction. A unique_lock can be created without immediately locking, can unlock at any point in its existence, and can transfer ownership of the lock from one instance to another.

What is the benefit of using std :: unique_lock?

The benefit to using std::unique_lock<> comes from two things: you can transfer ownership of the lock between instances, and. the std::unique_lock<> object does not have to own the lock on the mutex it is associated with.

Does unique_lock automatically unlock?

The std::scoped_lock and std::unique_lock objects automate some aspects of locking, because they are capable of automatically unlocking.

Does std :: Lock_guard block?

No. The critical section begins at the point of declaration of the guard. The guard is declared after the condition - so it is not guarded. If you need the condition to be guarded as well, then move the guard before the if statement.


1 Answers

Because this complicates the implementation for no significant benefit whatsoever, and hides the fact that std::lock_guard and std::unique_lock are aware of the type of the lock they're guarding at compile-time.

Your solution is a workaround for the fact that class template parameter deduction does not happen during construction - this is addressed in the upcoming standard.

Necessitating to specify the lock type is annoying boilerplate that will be solved in C++17 (not only for lock guards) thanks to the Template parameter deduction for constructors (P0091R3) proposal.

The proposal (which was accepted), allows template parameters to be deduced from constructors, removing the need for make_xxx(...) helper functions or explicitly specify typenames that the compiler should be able to deduce:

// Valid C++17 for(size_t i = 0; i < num; ++i) {     std::mutex m;     std::unique_lock l(m); } 
like image 160
Vittorio Romeo Avatar answered Sep 21 '22 04:09

Vittorio Romeo