ReaderWriterLockSlim 클래스
동시에 여러개의 쓰레드가 공유 자원을 읽을 수 있지만 쓰기 작업이 수행되는 동안에는 상호 배타적으로 막는다.
01 EnterReadLock
읽기 잠금을 획득
public void EnterReadLock ();
02 EnterWriteLock
쓰기 잠금을 획득
public void EnterWriteLock ();
03 ExitReadLock
ReadLock을 해제 한 후 다른 쓰레드가 write에 액세스 할 수 있도록 한다. 여러번 획득 한 경우 동일한 수의 ExitReadLock이 필요하다.
public void ExitReadLock ();
04 ExitWriteLock
WriteLock을 보유한 쓰레드가 lock을 해제하여 다른 쓰레드가 액세스 할수 있도록 한다.
public void ExitWriteLock ();
2. 사용자 정의 Lock함수 구현하기 (재귀 X)
01 flag변수(32비트로 구성된 정수)
int flag = EMPTY_FLAG;
EMPTY_FLAG
비트 값이 모두 0으로 초기화된 상태
const int EMPTY_FLAG = 0x00000000;
WRITE_MASK
flag변수에서 WriteThreadId(쓰레드의 식별자)를 표시하는데 사용된다.
const int WRITE_MASK = 0x7FFF0000;
READ_MASK
ReadCount를 표현하는데 사용된다. ReadCount는 ReadLock을 획득한 쓰레드의 수를 나타낸다.
const int READ_MASK = 0x0000FFFF;
02 WriteLock
const int MAX_SPIN_COUNT = 5000;
int flag = EMPTY_FLAG;
public void WriteLock()
{
// 아무도 WriteLock or ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
// 락이 획득할때 까지 반복된다.
while(true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
// CompareExchange 메서드를 사용하여 flag 변수와 EMPTY_FLAG를 비교하고, 두 값이 같을 경우 desired 값으로 변경한다.
// 변경에 성공하면 원래 flag 값을 반환하고, 실패하면 현재 flag 값을 반환한다.
if (Interlocked.CompareExchange(ref flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
return;
}
// 일정 횟수의 시도가 실패한 경우, 다른 스레드에 양보(Yield)하여 CPU 자원을 더 공정하게 분배한다.
Thread.Yield();
}
}
코드 설명
1) desired 변수
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
Thread.CurrentThread.ManagedThreadId의 값을 desired변수의 WriteThreadId에 위치 시키기 위해 << 16 shift를 진행한다.
2) Writelock loop 설명
CompareExchange를 통해 lock 획득을 시도 한다.
if (Interlocked.CompareExchange(ref flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
return;
flag값과 EMPTY_FLAG값을 비교하고 값이 같다면 flag에 desired값을 대입하고 반환 값은 원래 flag값이 반환된다.
반환된 값이 EMPTY_FLAG와 값이 일치하면 lock을 획득하였음을 의미하고 return되면서 메서드가 종료된다.
3) Yield
스핀 횟수가 MAX_SPIN_COUNT보다 초과될 경우 양보하여 다른 쓰레드가 사용할 수 있도록 한다.
03 WriteUnlock
flag의 값을 EMPTY_FLAG로 변경하면서 락을 해제한다.
public void WriteUnlock()
{
Interlocked.Exchange(ref flag, EMPTY_FLAG);
}
04 Readlock
public void Readlock()
{
while(true)
{
for(int i = 0; i<MAX_SPIN_COUNT;i++)
{
int expected = (flag & READ_MASK);
// 아무도 WriteLock을 획득하고 있지 않을 때, ReadCount를 1 늘린다.
if (Interlocked.CompareExchange(ref flag, expected + 1 , expected) == expected)// A(0->1)승자 B(0->1) 실패 -> 다시시도
return;
}
Thread.Yield();
}
}
코드설명
1) expected 변수
WRITE_MASK가 없다고 가정한 값
int expected = (flag & READ_MASK);
2) ReadLock loop
CompareExchange를 통해 lock 획득을 시도 한다.
if (Interlocked.CompareExchange(ref flag, expected + 1, expected) == expected)
return;
Lock 획득 성공한 경우
flag값과 expected값을 비교하고 값이 같다면 flag를 expected + 1로 바꿔 ReadCount를 1증가 시킨다. ReadLock을 획득하였으므로 함수를 종료하여 Read작업을 수행하도록 한다.
Lock 획득 실패한 경우
두 쓰레드가 동시에 획득하려는 상황에서 획득을 실패한 쓰레드는 다시 시도 해야 한다.
05 ReadUnlock
flag의 하위 16비트가 ReadCount이므로 값을 1 감소 시키면서 lock을 해제 한다.
public void ReadUnLock()
{
Interlocked.Decrement(ref flag);
}
3. 사용자 정의 Lock함수 구현하기 (재귀 O)
WriteLock -> WriteLock(OK), WriteLock -> ReadLock(OK), ReadLock -> WriteLock(No)
01 재귀 X와 WriteLock에서 다른 부분
int writeCount = 0;// 몇번 재귀되었는지 횟수
public void WriteLock()
{
// 동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (flag & WRITE_MASK) >> 16; // Id추출
if(Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
writeCount++;
return;
}
}
현재 쓰레드와 lockThreadId가 동일한 경우 writeCount를 증가시키고 함수를 반환한다. 이는 쓰레드가 여러 번 WriteLock을 호출할 때 재귀적으로 Lock을 획득한다.
public void WriteLock()
{
while(true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
if (Interlocked.CompareExchange(ref flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
{
writeCount = 1;
return;
}
}
}
}
재귀적으로 호출되는 경우가 아닌 경우는 writeCount를 1로 설정한다.
02 재귀 X와 UnWriteLock에서 다른 부분
WriteLock을 반납하면서 writeCount값을 감소시키고, 모든 WriteLock이 반납되었을때 flag값을 초기화한다.
public void WriteUnlock()
{
int lockCount = --writeCount;
if(lockCount == 0)
{
Interlocked.Exchange(ref flag, EMPTY_FLAG);
}
}
03 재귀 X와 ReadLock에서 다른 부분
flag의 하위 16비트가 ReadCount이므로 값을 1 증가시킨다.
public void Readlock()
{
// WriteLock을 소유한 쓰레드 가져오기
int lockThreadId = (flag & WRITE_MASK) >> 16;
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
// readCount를 증가시킨다.
Interlocked.Increment(ref flag);
return;
}
}
1) lockThreadId 변수
WriteLock을 획득한 쓰레드의 Id
2) WriteLock을 이미 획득한 쓰레드인지 확인
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
현재 실행중인 쓰레드의 ManagerThreadId가 lockThreadId와 같은지 비교한다. 현재 쓰레드가 WriteLock을 이미 획득한 쓰레드인지 확인 하는 용도이다.
이를 확인 하는 이유
ReadLock은 은 여러 쓰레드가 동시에 획득할 수 있지만 WriteLock은 해당 쓰레드가 작업을 완료하기 전까지 다른 쓰레드들이 ReadLock을 획득해서는 안된다. 그러므로 WriteLock을 확득한 쓰레드와 동일한 쓰레드만이 ReadLock을 획득할 수 있다.
실행 코드 예시
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static volatile int count = 0;
static Lock _lock = new Lock();
static void Main(string[] args)
{
Task t1 = new Task(delegate()
{
for(int i = 0; i< 100; i++)
{
_lock.WriteLock();
count++;
_lock.WriteUnlock();
}
});
Task t2 = new Task(delegate ()
{
for (int i = 0; i < 100; i++)
{
_lock.WriteLock();
count--;
_lock.WriteUnlock();
}
});
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(count);
}
}
}
결과 0이 잘 나오는것을 확인할 수 있다.
참고 : 본 내용은 MMORPG PART4 강의를 수강하여 작성하였습니다.
https://www.inflearn.com/course/%EC%9C%A0%EB%8B%88%ED%8B%B0-mmorpg-%EA%B0%9C%EB%B0%9C-part4
'cs공부 > 운영체제' 카테고리의 다른 글
운영체제 - 병렬처리(Parallel Loops, Parallel Invoke,Parallel Partitioning) (0) | 2023.06.06 |
---|---|
운영체제 - TLS(Thread Local Storage) (0) | 2023.06.06 |
운영체제 - 제3자에게 부탁하기(AutoResetEvent,Manualresetevent ) (0) | 2023.05.31 |
운영체제 - 캐시메모리, 캐시 컨트롤러,레지스터, RAM의 상호 작용(캐시 미스(Cache Miss),캐시 히트(Cache Hit) ) (0) | 2023.05.26 |
운영체제 - 커널(Kernel), 커널모드, 사용자모드 (0) | 2023.05.26 |
댓글