namespace ServerCore
{
// 구현 정책
// 1. 재귀적 락은 허용하지 않는다.
// 2. 스핀락은 5000번 시도한 후에 Yield로 양보한다.
public class ReaderWriterLock
{
private const int EMPTY_FLAG = 0x00000000;
private const int WRITE_MASK = 0x7FFF0000;
private const int READ_MASK = 0x0000FFFF;
private const int MAX_SPIN_COUNT = 5000;
// 32 bits : [Unused(1)] [WriteThread(15)] [ReadCount(16)]
private int _flag = EMPTY_FLAG;
public void WriteLock()
{
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
while (true)
{
for (var i = 0; i < MAX_SPIN_COUNT; i++)
// 아무도 사용하고 있지 않다면
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
return;
Thread.Yield();
}
}
public void WriteUnlock()
{
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
while (true)
{
// 아무도 WriteLock을 사용하고 있지 않다면, ReadLock을 1 증가시킨다.
for (var i = 0; i < MAX_SPIN_COUNT; i++)
{
int expected = (_flag & READ_MASK); // write lock을 다 무시하고 read lock만 확인
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}
이 코드는 ServerCore
네임스페이스 내의 ReaderWriterLock
클래스이다. 이 클래스는 읽기-쓰기 잠금을 구현하여 여러 스레드가 안전하게 데이터에 접근할 수 있도록 보장한다. 구현 정책은 재귀적 잠금을 허용하지 않으며, 스핀락을 최대 5000번 시도한 후 잠금을 얻지 못하면 스레드에게 실행 순서를 양보한다는 것이다.
주요 상수와 변수 설명
EMPTY_FLAG
: 초기 상태를 나타내며, 아무도 잠금을 사용하지 않을 때0x00000000
의 값을 갖는다.WRITE_MASK
: 쓰기 잠금을 위한 비트 마스크로,0x7FFF0000
값을 갖는다. 상위 15비트를 사용하여 쓰기 스레드를 식별한다.READ_MASK
: 읽기 잠금만을 확인하기 위한 마스크로, 하위 16비트를 나타낸다.MAX_SPIN_COUNT
: 스핀락을 시도하는 최대 횟수로, 이 횟수에 도달하면 스레드가Thread.Yield()
로 실행을 양보한다._flag
: 현재 잠금 상태를 나타내는 32비트 변수이다. 비트 구성은[Unused(1)] [WriteThread(15)] [ReadCount(16)]
로 되어 있다.
메소드 설명
WriteLock
메소드- 현재 스레드 ID를 사용해 쓰기 잠금을 시도한다. 스레드 ID를 16비트 왼쪽으로 이동해
_flag
의 상위 비트에 설정하고, 이를WRITE_MASK
와 결합해desired
값을 만든다. MAX_SPIN_COUNT
만큼_flag
가EMPTY_FLAG
인지 확인하며 반복 시도한다._flag
가 비어 있다면Interlocked.CompareExchange
를 사용해_flag
를desired
값으로 변경하여 잠금을 얻는다.- 잠금을 얻지 못한 경우,
Thread.Yield()
를 호출해 다른 스레드에게 실행을 양보한다.
- 현재 스레드 ID를 사용해 쓰기 잠금을 시도한다. 스레드 ID를 16비트 왼쪽으로 이동해
WriteUnlock
메소드Interlocked.Exchange
를 통해_flag
값을EMPTY_FLAG
로 초기화하여 쓰기 잠금을 해제한다.
ReadLock
메소드- 쓰기 잠금이 설정되어 있지 않을 때, 읽기 잠금을 1씩 증가시킨다.
_flag
의 하위 16비트를READ_MASK
로 확인하고, 해당 값에 1을 더해Interlocked.CompareExchange
로 갱신한다. MAX_SPIN_COUNT
만큼 시도한 후에도 잠금을 얻지 못하면 다시 반복한다.
- 쓰기 잠금이 설정되어 있지 않을 때, 읽기 잠금을 1씩 증가시킨다.
ReadUnlock
메소드Interlocked.Decrement
를 사용해_flag
의 읽기 잠금을 1 감소시켜 읽기 잠금을 해제한다.
코드의 핵심 동작
이 클래스는 쓰기와 읽기 잠금을 동시에 관리할 수 있는 간단한 락 시스템이다. 쓰기 잠금은 한 번에 한 스레드만이 설정할 수 있으며, 읽기 잠금은 쓰기 잠금이 걸려 있지 않은 경우에 여러 스레드가 동시에 설정할 수 있다. Interlocked
메소드는 원자적 작업을 보장하여 여러 스레드가 _flag
변수에 안전하게 접근할 수 있도록 한다.
'공부 > 게임서버' 카테고리의 다른 글
경합 조건 (Race Condition), Interlocked (0) | 2024.11.11 |
---|---|
메모리 영역과 스레드 (0) | 2024.11.11 |
임계영역 (Critical Section) (0) | 2024.11.10 |
가시성 문제와 보장 방법 (0) | 2024.11.10 |
SpinLock / Interlocked.Exchange (0) | 2024.11.10 |