Skip to content
ozt88 edited this page Apr 28, 2015 · 3 revisions

Reader-Writer Lock

Read와 Write는 자원을 사용하는 일반적인 방법이다. 기존의 Mutex 방식 Lock은 같은 자원에 대해서 Read이건 Write이건 배타적인 사용방식을 고수했다. 자원을 Read하고 있으면 다른 스레드가 그 자원에 Write작업을 수행할 수 없고, 그 반대의 경우도 마찬가지이0다. 누군가 새로 메모리에 새로운 값을 입력하는 도중에 그 값을 읽으면 내용의 일관성이 깨지기 때문에 일견 타당할 수 있다. 하지만 Read와 Read의 관계에서 다시 한번 생각해보자. 단순 읽기 동작은 내용의 일관성을 해치지 않기 때문에 서로 동시에 같은 내용을 읽어도 동기화 문제가 발생하지 않는다. Read끼리는 락을 하지 않는다면, 좀더 자원을 효율적으로 사용할 수 있지 않을까? 이런 아이디어에서 시작한 새로운 자원관리 기법이 Read-Write Lock(이하 RW lock)이다.

RW lock 작동 방식

기존 Mutex는 lock, unlock 두가지 상태만이 존재했다. 하지만 RW lock은 좀더 고려해야할 경우가 있기 때문에 read-lock, write-lock, unlock의 세가지 상태가 존재한다. unlock의 경우는 Mutex와 다를 바 없기에 read-lock과 write-lock에서 자원 관리하는 방식만 알아두면 된다.

  • read-lock은 말 그대로 누군가 자원을 Read하고 있을때 걸리는 RW lock의 상태이다. 한 스레드라도 자원을 읽는다면 자원의 RW lock은 read-lock상태가 된다. 이 상태에서 Read요청이 들어오면 통과시키고, Write요청이 들어오면 블럭시킨다. 일반적으로 write 작업이 기아상태에 빠지지 않기 위해서, 대기하는 write요청이 있으면 다른 read요청을 블럭시킨다고 한다.

  • write-lock은 누군가 자원을 Write하고 있을때 걸리는 RW lock의 상태이다. write-lock 상태에 있는 RW lock은 들어오는 모든 자원 요청에 대해서 블럭시킨다. 쓰기 작업은 완료될때까지 자원의 일관성을 보장할 수 없기 때문이다.

UNIX표준 RW lock : pthread_rwlock 사용방법

//초기화
int pthread_rwlock_init(pthread_rwlock_t* lock, const pthread_rwlockattr_t* attr);

//해제
int pthread_rwlock_destroy(pthread_rwlock_t* lock);

//read-lock
int pthread_rwlock_rdlock(pthread_rwlock_t* lock);

//write-lock
int pthread_rwlock_wrlock(pthread_rwlock_t* lock);

//unlock
int pthread_rwlock_unlock(pthread_rwlock_t* lock);

//try-version read-lock
int pthread_rwlock_tryrdlock(pthread_rwlock_t* lock);

//try-version write-lock
int pthread_rwlock_trywrlock(pthread_rwlock_t* lock);

try-version은 lock 시도하여 성공 실패에 따라서 반환값을 달리하는 non-blocking 형태의 RW lock 수행방법

직접 RW lock 구현하기

windows에서 pthread_rwlock을 지원하지 않으니 직접 만들어 써야한다. 그렇게 어렵지 않으니 아래 예제 코드를 따라가면서 같이 만들어 써보자.

//RWLock.h
class RWLock
{
public:
   RWLock();
   ~RWLock();

   RWLock(const RWLock& rhs) = delete;
   RWLock& operator=(const RWLock& rhs) = delete;

   /// exclusive mode
   void EnterWriteLock();
   void LeaveWriteLock();

   /// shared mode
   void EnterReadLock();
   void LeaveReadLock();

   long GetLockFlag() const { return mLockFlag;  }
private:
   enum LockFlag
   {
      LF_WRITE_MASK	= 0x7FF00000,
      LF_WRITE_FLAG	= 0x00100000,
      LF_READ_MASK	= 0x000FFFFF ///< 하위 20비트를 readlock을 위한 플래그로 사용한다.
   };
   volatile long mLockFlag;
};


//RWLock.cpp

void RWLock::EnterWriteLock()
{
   while (true)
   {
      /// 다른놈이 writelock 풀어줄때까지 기다린다.
      while (mLockFlag & LF_WRITE_MASK)
         YieldProcessor();
      if ((InterlockedAdd(&mLockFlag, LF_WRITE_FLAG) & LF_WRITE_MASK) == LF_WRITE_FLAG)
      {
         /// 다른놈이 readlock 풀어줄때까지 기다린다.
         while (mLockFlag & LF_READ_MASK)
            YieldProcessor();
         
         return;
      }
      InterlockedAdd(&mLockFlag, -LF_WRITE_FLAG);
   }
}

void RWLock::LeaveWriteLock()
{
   InterlockedAdd(&mLockFlag, -LF_WRITE_FLAG);
}

void FastSpinlock::EnterReadLock()
{
   while (true)
   {
      /// 다른놈이 writelock 풀어줄때까지 기다린다.
      while (mLockFlag & LF_WRITE_MASK)
         YieldProcessor();

      //WRITE LOCK 체크
      if((InterlockedIncrement(&mLockFlag) & LF_WRITE_MASK) == 0)
         return;
      else
         InterlockedDecrement(&mLockFlag);
   }
}

void FastSpinlock::LeaveReadLock()
{
   InterlockedDecrement(&mLockFlag);
}