Почему std::lock_guard
и std::unique_lock
необходимо указать тип блокировки в качестве параметра шаблона?Почему std :: lock_guard/std :: unique_lock использовать стирание типа?
Рассмотрите следующий вариант. Во-первых, в detail
пространстве имен, существуют классы типа стирания (нешаблонном абстрактный базовый класс и шаблон производный класс):
#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;
};
}
Теперь te_lock_guard
, тип стиранием замок охранник, просто места размещения новостей объект правильного типа при сконструированной (без динамического выделения памяти):
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;
};
Я проверил производительность по сравнению с классами из стандартной библиотеки:
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;
}
}
Используя g ++ с -O3
, статистически значимых потерь производительности нет.
что такое 'статистически значимое' для вас? Нативная реализация показалась быстрее после запуска 4-5 раз. Поэтому не уверен, что вы считаете незначительным. И почему выбрать реализацию по умолчанию, которая медленнее текущей альтернативы? Однако сам вопрос довольно интересен. Хороший кто-то измерил альтернативу. – Hayt
Если вы действительно были слишком расстроены повторением ввода типа, вы можете использовать шаблон функции: 'auto && lock = guard_me (mx);' ([Demo] (https://ideone.com/ysxJEz) .) –
@Hayt Я имел в виду, что статистический тест перестановки не отвергал гипотезы, что они принадлежат к одному и тому же распределению. Между прочим, сложная альтернатива иногда бывает быстрее, кстати. В статистике я стараюсь избегать сильных утверждений, таких как «они работают с одинаковой скоростью». –