Ricerca…
Osservazioni
È meglio usare std :: shared_mutex di std :: shared_timed_mutex .
La differenza di prestazioni è più del doppio.
Se vuoi usare RWLock, troverai che ci sono due opzioni.
È std :: shared_mutex e shared_timed_mutex.
potresti pensare che std :: shared_timed_mutex sia solo la versione 'std :: shared_mutex + time method'.
Ma l'implementazione è completamente diversa.
Il codice seguente è l'implementazione di MSVC14.1 di std :: shared_mutex.
class shared_mutex
{
public:
typedef _Smtx_t * native_handle_type;
shared_mutex() _NOEXCEPT
: _Myhandle(0)
{ // default construct
}
~shared_mutex() _NOEXCEPT
{ // destroy the object
}
void lock() _NOEXCEPT
{ // lock exclusive
_Smtx_lock_exclusive(&_Myhandle);
}
bool try_lock() _NOEXCEPT
{ // try to lock exclusive
return (_Smtx_try_lock_exclusive(&_Myhandle) != 0);
}
void unlock() _NOEXCEPT
{ // unlock exclusive
_Smtx_unlock_exclusive(&_Myhandle);
}
void lock_shared() _NOEXCEPT
{ // lock non-exclusive
_Smtx_lock_shared(&_Myhandle);
}
bool try_lock_shared() _NOEXCEPT
{ // try to lock non-exclusive
return (_Smtx_try_lock_shared(&_Myhandle) != 0);
}
void unlock_shared() _NOEXCEPT
{ // unlock non-exclusive
_Smtx_unlock_shared(&_Myhandle);
}
native_handle_type native_handle() _NOEXCEPT
{ // get native handle
return (&_Myhandle);
}
shared_mutex(const shared_mutex&) = delete;
shared_mutex& operator=(const shared_mutex&) = delete;
private:
_Smtx_t _Myhandle;
};
void __cdecl _Smtx_lock_exclusive(_Smtx_t * smtx)
{ /* lock shared mutex exclusively */
AcquireSRWLockExclusive(reinterpret_cast<PSRWLOCK>(smtx));
}
void __cdecl _Smtx_lock_shared(_Smtx_t * smtx)
{ /* lock shared mutex non-exclusively */
AcquireSRWLockShared(reinterpret_cast<PSRWLOCK>(smtx));
}
int __cdecl _Smtx_try_lock_exclusive(_Smtx_t * smtx)
{ /* try to lock shared mutex exclusively */
return (TryAcquireSRWLockExclusive(reinterpret_cast<PSRWLOCK>(smtx)));
}
int __cdecl _Smtx_try_lock_shared(_Smtx_t * smtx)
{ /* try to lock shared mutex non-exclusively */
return (TryAcquireSRWLockShared(reinterpret_cast<PSRWLOCK>(smtx)));
}
void __cdecl _Smtx_unlock_exclusive(_Smtx_t * smtx)
{ /* unlock exclusive shared mutex */
ReleaseSRWLockExclusive(reinterpret_cast<PSRWLOCK>(smtx));
}
void __cdecl _Smtx_unlock_shared(_Smtx_t * smtx)
{ /* unlock non-exclusive shared mutex */
ReleaseSRWLockShared(reinterpret_cast<PSRWLOCK>(smtx));
}
Puoi vedere che std :: shared_mutex è implementato in Windows Slim Reader / Write Locks ( https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa904937(v=vs.85).aspx)
Ora esaminiamo l'implementazione di std :: shared_timed_mutex.
Il codice seguente è l'implementazione di MSVC14.1 di std :: shared_timed_mutex.
class shared_timed_mutex
{
typedef unsigned int _Read_cnt_t;
static constexpr _Read_cnt_t _Max_readers = _Read_cnt_t(-1);
public:
shared_timed_mutex() _NOEXCEPT
: _Mymtx(), _Read_queue(), _Write_queue(),
_Readers(0), _Writing(false)
{ // default construct
}
~shared_timed_mutex() _NOEXCEPT
{ // destroy the object
}
void lock()
{ // lock exclusive
unique_lock<mutex> _Lock(_Mymtx);
while (_Writing)
_Write_queue.wait(_Lock);
_Writing = true;
while (0 < _Readers)
_Read_queue.wait(_Lock); // wait for writing, no readers
}
bool try_lock()
{ // try to lock exclusive
lock_guard<mutex> _Lock(_Mymtx);
if (_Writing || 0 < _Readers)
return (false);
else
{ // set writing, no readers
_Writing = true;
return (true);
}
}
template<class _Rep,
class _Period>
bool try_lock_for(
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // try to lock for duration
return (try_lock_until(chrono::steady_clock::now() + _Rel_time));
}
template<class _Clock,
class _Duration>
bool try_lock_until(
const chrono::time_point<_Clock, _Duration>& _Abs_time)
{ // try to lock until time point
auto _Not_writing = [this] { return (!_Writing); };
auto _Zero_readers = [this] { return (_Readers == 0); };
unique_lock<mutex> _Lock(_Mymtx);
if (!_Write_queue.wait_until(_Lock, _Abs_time, _Not_writing))
return (false);
_Writing = true;
if (!_Read_queue.wait_until(_Lock, _Abs_time, _Zero_readers))
{ // timeout, leave writing state
_Writing = false;
_Lock.unlock(); // unlock before notifying, for efficiency
_Write_queue.notify_all();
return (false);
}
return (true);
}
void unlock()
{ // unlock exclusive
{ // unlock before notifying, for efficiency
lock_guard<mutex> _Lock(_Mymtx);
_Writing = false;
}
_Write_queue.notify_all();
}
void lock_shared()
{ // lock non-exclusive
unique_lock<mutex> _Lock(_Mymtx);
while (_Writing || _Readers == _Max_readers)
_Write_queue.wait(_Lock);
++_Readers;
}
bool try_lock_shared()
{ // try to lock non-exclusive
lock_guard<mutex> _Lock(_Mymtx);
if (_Writing || _Readers == _Max_readers)
return (false);
else
{ // count another reader
++_Readers;
return (true);
}
}
template<class _Rep,
class _Period>
bool try_lock_shared_for(
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // try to lock non-exclusive for relative time
return (try_lock_shared_until(_Rel_time
+ chrono::steady_clock::now()));
}
template<class _Time>
bool _Try_lock_shared_until(_Time _Abs_time)
{ // try to lock non-exclusive until absolute time
auto _Can_acquire = [this] {
return (!_Writing && _Readers < _Max_readers); };
unique_lock<mutex> _Lock(_Mymtx);
if (!_Write_queue.wait_until(_Lock, _Abs_time, _Can_acquire))
return (false);
++_Readers;
return (true);
}
template<class _Clock,
class _Duration>
bool try_lock_shared_until(
const chrono::time_point<_Clock, _Duration>& _Abs_time)
{ // try to lock non-exclusive until absolute time
return (_Try_lock_shared_until(_Abs_time));
}
bool try_lock_shared_until(const xtime *_Abs_time)
{ // try to lock non-exclusive until absolute time
return (_Try_lock_shared_until(_Abs_time));
}
void unlock_shared()
{ // unlock non-exclusive
_Read_cnt_t _Local_readers;
bool _Local_writing;
{ // unlock before notifying, for efficiency
lock_guard<mutex> _Lock(_Mymtx);
--_Readers;
_Local_readers = _Readers;
_Local_writing = _Writing;
}
if (_Local_writing && _Local_readers == 0)
_Read_queue.notify_one();
else if (!_Local_writing && _Local_readers == _Max_readers - 1)
_Write_queue.notify_all();
}
shared_timed_mutex(const shared_timed_mutex&) = delete;
shared_timed_mutex& operator=(const shared_timed_mutex&) = delete;
private:
mutex _Mymtx;
condition_variable _Read_queue, _Write_queue;
_Read_cnt_t _Readers;
bool _Writing;
};
class stl_condition_variable_win7 final : public stl_condition_variable_interface
{
public:
stl_condition_variable_win7()
{
__crtInitializeConditionVariable(&m_condition_variable);
}
~stl_condition_variable_win7() = delete;
stl_condition_variable_win7(const stl_condition_variable_win7&) = delete;
stl_condition_variable_win7& operator=(const stl_condition_variable_win7&) = delete;
virtual void destroy() override {}
virtual void wait(stl_critical_section_interface *lock) override
{
if (!stl_condition_variable_win7::wait_for(lock, INFINITE))
std::terminate();
}
virtual bool wait_for(stl_critical_section_interface *lock, unsigned int timeout) override
{
return __crtSleepConditionVariableSRW(&m_condition_variable, static_cast<stl_critical_section_win7 *>(lock)->native_handle(), timeout, 0) != 0;
}
virtual void notify_one() override
{
__crtWakeConditionVariable(&m_condition_variable);
}
virtual void notify_all() override
{
__crtWakeAllConditionVariable(&m_condition_variable);
}
private:
CONDITION_VARIABLE m_condition_variable;
};
Puoi vedere che std :: shared_timed_mutex è implementato in std :: condition_value.
Questa è un'enorme differenza.
Quindi controlliamo le prestazioni di due di loro.
Questo è il risultato del test di lettura / scrittura per 1000 millisecondi.
std :: shared_mutex ha elaborato read / write più di 2 volte rispetto a std :: shared_timed_mutex.
In questo esempio, il rapporto di lettura / scrittura è lo stesso, ma la frequenza di lettura è più frequente della velocità di scrittura in tempo reale.
Pertanto, la differenza di prestazioni può essere maggiore.
il codice qui sotto è il codice in questo esempio.
void useSTLSharedMutex()
{
std::shared_mutex shared_mtx_lock;
std::vector<std::thread> readThreads;
std::vector<std::thread> writeThreads;
std::list<int> data = { 0 };
volatile bool exit = false;
std::atomic<int> readProcessedCnt(0);
std::atomic<int> writeProcessedCnt(0);
for (unsigned int i = 0; i < std::thread::hardware_concurrency(); i++)
{
readThreads.push_back(std::thread([&data, &exit, &shared_mtx_lock, &readProcessedCnt]() {
std::list<int> mydata;
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock_shared();
mydata.push_back(data.back());
++localProcessCnt;
shared_mtx_lock.unlock_shared();
if (exit)
break;
}
std::atomic_fetch_add(&readProcessedCnt, localProcessCnt);
}));
writeThreads.push_back(std::thread([&data, &exit, &shared_mtx_lock, &writeProcessedCnt]() {
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock();
data.push_back(rand() % 100);
++localProcessCnt;
shared_mtx_lock.unlock();
if (exit)
break;
}
std::atomic_fetch_add(&writeProcessedCnt, localProcessCnt);
}));
}
std::this_thread::sleep_for(std::chrono::milliseconds(MAIN_WAIT_MILLISECONDS));
exit = true;
for (auto &r : readThreads)
r.join();
for (auto &w : writeThreads)
w.join();
std::cout << "STLSharedMutex READ : " << readProcessedCnt << std::endl;
std::cout << "STLSharedMutex WRITE : " << writeProcessedCnt << std::endl;
std::cout << "TOTAL READ&WRITE : " << readProcessedCnt + writeProcessedCnt << std::endl << std::endl;
}
void useSTLSharedTimedMutex()
{
std::shared_timed_mutex shared_mtx_lock;
std::vector<std::thread> readThreads;
std::vector<std::thread> writeThreads;
std::list<int> data = { 0 };
volatile bool exit = false;
std::atomic<int> readProcessedCnt(0);
std::atomic<int> writeProcessedCnt(0);
for (unsigned int i = 0; i < std::thread::hardware_concurrency(); i++)
{
readThreads.push_back(std::thread([&data, &exit, &shared_mtx_lock, &readProcessedCnt]() {
std::list<int> mydata;
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock_shared();
mydata.push_back(data.back());
++localProcessCnt;
shared_mtx_lock.unlock_shared();
if (exit)
break;
}
std::atomic_fetch_add(&readProcessedCnt, localProcessCnt);
}));
writeThreads.push_back(std::thread([&data, &exit, &shared_mtx_lock, &writeProcessedCnt]() {
int localProcessCnt = 0;
while (true)
{
shared_mtx_lock.lock();
data.push_back(rand() % 100);
++localProcessCnt;
shared_mtx_lock.unlock();
if (exit)
break;
}
std::atomic_fetch_add(&writeProcessedCnt, localProcessCnt);
}));
}
std::this_thread::sleep_for(std::chrono::milliseconds(MAIN_WAIT_MILLISECONDS));
exit = true;
for (auto &r : readThreads)
r.join();
for (auto &w : writeThreads)
w.join();
std::cout << "STLSharedTimedMutex READ : " << readProcessedCnt << std::endl;
std::cout << "STLSharedTimedMutex WRITE : " << writeProcessedCnt << std::endl;
std::cout << "TOTAL READ&WRITE : " << readProcessedCnt + writeProcessedCnt << std::endl << std::endl;
}
std :: unique_lock, std :: shared_lock, std :: lock_guard
Utilizzato per l'acquisizione in stile RAII di blocchi di prova, serrature temporizzate di prova e serrature ricorsive.
std::unique_lock
consente la proprietà esclusiva dei mutex.
std::shared_lock
consente la proprietà condivisa dei mutex. Diversi thread possono contenere std::shared_locks
su uno std::shared_mutex
. Disponibile da C ++ 14.
std::lock_guard
è un'alternativa leggera a std::unique_lock
e std::shared_lock
.
#include <unordered_map>
#include <mutex>
#include <shared_mutex>
#include <thread>
#include <string>
#include <iostream>
class PhoneBook {
public:
std::string getPhoneNo( const std::string & name )
{
std::shared_lock<std::shared_timed_mutex> l(_protect);
auto it = _phonebook.find( name );
if ( it != _phonebook.end() )
return (*it).second;
return "";
}
void addPhoneNo ( const std::string & name, const std::string & phone )
{
std::unique_lock<std::shared_timed_mutex> l(_protect);
_phonebook[name] = phone;
}
std::shared_timed_mutex _protect;
std::unordered_map<std::string,std::string> _phonebook;
};
Strategie per le classi di blocco: std :: try_to_lock, std :: adopt_lock, std :: defer_lock
Quando si crea uno std :: unique_lock, ci sono tre diverse strategie di blocco tra cui scegliere: std::try_to_lock
, std::defer_lock
e std::adopt_lock
-
std::try_to_lock
consente di provare un blocco senza bloccare:
{
std::atomic_int temp {0};
std::mutex _mutex;
std::thread t( [&](){
while( temp!= -1){
std::this_thread::sleep_for(std::chrono::seconds(5));
std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);
if(lock.owns_lock()){
//do something
temp=0;
}
}
});
while ( true )
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);
if(lock.owns_lock()){
if (temp < INT_MAX){
++temp;
}
std::cout << temp << std::endl;
}
}
}
-
std::defer_lock
consente di creare una struttura di blocco senza acquisire il blocco. Quando si blocca più di un mutex, esiste una finestra di opportunità per un deadlock se due chiamanti di funzione cercano di acquisire i blocchi contemporaneamente:
{
std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock);
lock1.lock()
lock2.lock(); // deadlock here
std::cout << "Locked! << std::endl;
//...
}
Con il seguente codice, qualunque cosa accada nella funzione, i blocchi vengono acquisiti e rilasciati nell'ordine appropriato:
{
std::unique_lock<std::mutex> lock1(_mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(_mutex2, std::defer_lock);
std::lock(lock1,lock2); // no deadlock possible
std::cout << "Locked! << std::endl;
//...
}
-
std::adopt_lock
non tenta di bloccarsi una seconda volta se il thread chiamante attualmente possiede il lock.
{
std::unique_lock<std::mutex> lock1(_mutex1, std::adopt_lock);
std::unique_lock<std::mutex> lock2(_mutex2, std::adopt_lock);
std::cout << "Locked! << std::endl;
//...
}
Qualcosa da tenere a mente è che std :: adopt_lock non è un sostituto per l'uso mutex ricorsivo. Quando il blocco esce dall'ambito, il mutex viene rilasciato .
std :: mutex
std :: mutex è una struttura di sincronizzazione semplice e non ricorsiva che viene utilizzata per proteggere i dati a cui si accede da più thread.
std::atomic_int temp{0};
std::mutex _mutex;
std::thread t( [&](){
while( temp!= -1){
std::this_thread::sleep_for(std::chrono::seconds(5));
std::unique_lock<std::mutex> lock( _mutex);
temp=0;
}
});
while ( true )
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
std::unique_lock<std::mutex> lock( _mutex, std::try_to_lock);
if ( temp < INT_MAX )
temp++;
cout << temp << endl;
}
std :: scope_lock (C ++ 17)
std::scoped_lock
fornisce semantica di stile RAII per possedere un altro mutex, combinato con gli algoritmi di lock lock usati da std::lock
. Quando std::scoped_lock
viene distrutto, i mutex vengono rilasciati nell'ordine inverso rispetto al quale sono stati acquisiti.
{
std::scoped_lock lock{_mutex1,_mutex2};
//do something
}
Tipi di mutex
C ++ 1x offre una selezione di classi mutex:
- std :: mutex - offre funzionalità di blocco semplici.
- std :: timed_mutex - offre la funzionalità try_to_lock
- std :: recursive_mutex - consente il blocco ricorsivo con lo stesso thread.
- std :: shared_mutex, std :: shared_timed_mutex - offre funzionalità di blocco condivise e univoche.
std :: blocco
std::lock
utilizza algoritmi di deadlock avoidance per bloccare uno o più mutex. Se durante una chiamata viene generata un'eccezione per bloccare più oggetti, std::lock
sblocca gli oggetti bloccati correttamente prima di rilanciare l'eccezione.
std::lock(_mutex1, _mutex2);