수색…
비고
std :: shared_timed_mutex 보다 std :: shared_mutex 를 사용하는 것이 더 좋습니다.
성능 차이는 두 배 이상입니다.
RWLock을 사용하려면 두 가지 옵션이 있습니다.
그것은 std :: shared_mutex와 shared_timed_mutex입니다.
당신은 std :: shared_timed_mutex가 단지 'std :: shared_mutex + time method'버전이라고 생각할 것입니다.
그러나 구현은 완전히 다릅니다.
아래의 코드는 std :: shared_mutex의 MSVC14.1 구현입니다.
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));
}
std :: shared_mutex가 Windows Slim Reader / Write Locks ( https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa904937(v=vs.85).aspx)에 구현되어 있음을 알 수 있습니다.
이제 std :: shared_timed_mutex의 구현을 살펴 보겠습니다.
아래의 코드는 std :: shared_timed_mutex의 MSVC14.1 구현입니다.
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;
};
std :: shared_timed_mutex가 std :: condition_value에 구현되어 있음을 알 수 있습니다.
이것은 큰 차이입니다.
그럼 두 가지의 성능을 확인해 봅시다.
이것은 1000 밀리 초 동안 읽기 / 쓰기 테스트의 결과입니다.
std :: shared_mutex가 std :: shared_timed_mutex보다 2 배 이상 읽기 / 쓰기 를 처리했습니다.
이 예에서 읽기 / 쓰기 비율은 동일하지만 읽기 속도는 실제 쓰기 속도보다 잦습니다.
따라서 성능 차이가 커질 수 있습니다.
아래 코드는이 예제의 코드입니다.
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
try lock, timed try lock 및 recursive lock을 획득하는 RAII 스타일에 사용됩니다.
std::unique_lock
은 뮤텍스의 독점적 소유권을 허용합니다.
std::shared_lock
은 뮤텍스의 공유 소유권을 허용합니다. 여러 스레드가 보유 할 수 std::shared_locks
A의 std::shared_mutex
. C ++에서 사용 가능 14.
std::lock_guard
는 std::unique_lock
및 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;
};
잠금 클래스에 대한 전략 : std :: try_to_lock, std :: ado_lock, std :: defer_lock
std :: unique_lock을 만들 때 선택할 수있는 3 가지 잠금 전략이 있습니다 : std::try_to_lock
, std::defer_lock
및 std::adopt_lock
-
std::try_to_lock
은 차단하지 않고 잠금을 시도 할 수 있습니다.
{
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
은 잠금을 획득하지 않고 잠금 구조를 생성합니다. 둘 이상의 뮤텍스를 잠글 때 두 함수 호출자가 동시에 잠금을 얻으려고하면 교착 상태가 발생할 수 있습니다.
{
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;
//...
}
다음 코드를 사용하여 함수에서 어떤 일이 발생하더라도 잠금은 적절한 순서로 획득되고 해제됩니다.
{
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
은 두 번째 잠금을 시도하지 않습니다.
{
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;
//...
}
염두에 두어야 할 점은 std :: ado_lock은 재귀 뮤텍스 사용을 대체 할 수 없다는 것입니다. 잠금이 범위를 벗어나면 뮤텍스가 해제 됩니다.
std :: 뮤텍스
std :: mutex는 여러 스레드가 액세스하는 데이터를 보호하는 데 사용되는 단순한 비 재귀 적 동기화 구조입니다.
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 :: scoped_lock (C ++ 17)
std::scoped_lock
은 std::lock
사용하는 잠금 회피 알고리즘과 결합 된 또 하나의 뮤텍스를 소유하는 RAII 스타일의 의미를 제공합니다. std::scoped_lock
이 파괴되면 뮤텍스는 획득 한 역순으로 해제됩니다.
{
std::scoped_lock lock{_mutex1,_mutex2};
//do something
}
뮤텍스 유형
C ++ 1x는 뮤텍스 클래스를 제공합니다 :
- std :: mutex - 간단한 잠금 기능을 제공합니다.
- std :: timed_mutex - try_to_lock 기능 제공
- std :: recursive_mutex - 동일한 스레드에 의한 재귀 적 잠금을 허용합니다.
- std :: shared_mutex, std :: shared_timed_mutex - 공유 및 고유 잠금 기능을 제공합니다.
std :: lock
std::lock
은 데드락 회피 알고리즘을 사용하여 하나 이상의 뮤텍스를 잠급니다. 여러 객체를 잠그기 위해 호출하는 동안 예외가 발생하면 std::lock
은 예외를 다시 throw하기 전에 성공적으로 잠긴 객체의 std::lock
해제합니다.
std::lock(_mutex1, _mutex2);