Dlaczego std::lock_guard
i std::unique_lock
wymagają określenia typu blokady jako parametru szablonu?Dlaczego std :: lock_guard/std :: unique_lock nie używa typu wymazania?
Rozważ następującą opcję. Po pierwsze, w detail
nazw, są zajęcia typu skasowaniem (non-szablon abstrakcyjne klasy bazowej, a szablon klasa pochodna):
#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;
};
}
Teraz te_lock_guard
, osłona zamka typu usuwanie, wystarczy umieszczenie-news przedmiot prawidłowego typu kiedy skonstruowanej (bez dynamicznej alokacji pamięci):
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;
};
Sprawdziłem wydajność w porównaniu z klasami biblioteki standardowej za:
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;
}
}
Korzystanie z g ++ z -O3
, nie ma statystycznie znaczącej utraty wydajności.
Co jest dla ciebie "istotne statystycznie"? Wydanie natywne wydawało się szybsze po uruchomieniu go 4-5 razy. Więc nie wiesz, co uważasz za nieistotne. I dlaczego wybrać domyślną implementację wolniejszą od obecnej? Samo pytanie jest dość interesujące. Dobry ktoś zmierzył alternatywę. – Hayt
Jeśli naprawdę byłeś zbyt zdenerwowany przez konieczność ponownego wpisania nazwy typu, możesz użyć szablonu funkcji: 'auto && lock = guard_me (mx);' ([Demo] (https://ideone.com/ysxJEz) .) –
@Hayt Co mam na myśli to, że statystyczny test permutacji nie odrzucił hipotez, że są z tego samego rozkładu. Według mnie skomplikowana alternatywa jest czasami szybsza, nawiasem mówiąc. Zajmując się statystykami, staram się unikać silnych stwierdzeń, takich jak "działają z tą samą szybkością". –