728x90
반응형
이전에 설명한 적이 있는 ReaderWriterLock에 대해 간단하게 구현해보도록 하겠습니다.
구현방식은 다음과 같습니다.
int 형의 비어있는 플래그를 사용하여 ReaderLock과 WriterLock을 구현합니다.
int는 4바이트 총 32비트로 구성되어 있으므로 다음과 같이 사용이 가능합니다.
- 1비트 = 사용안함
- 15비트 = WriteLock을 사용하여 자원을 점유하는 스레드 기록
- 16비트 = ReadLock을 사용하여 자원을 점유하는 스레드 기록
그리고 만들어지는 Lock은 스핀락을 사용하여 만들고 스핀락의 대기횟수를 제한하겠습니다.
그리고 Lock 안에서 yield를 통해 실행가능한 스레드에게 자원의 점유를 넘겨주겠습니다.
마지막으로 WriterLock의 경우 Lock 안에서 재귀적으로 다른 Lock을 설정하도록 하지만,
ReadLock의 경우는 그렇게 할 수 없도록 설정하겠습니다.
이제 실제 코드를 알아보겠습니다.
class Lock
{
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0x7FFF0000;
const int READ_MASK = 0x0000FFFF;
const int MAX_SPIN_COUNT = 5000;
int _flag;
int _writeCount = 0;
public void WriteLock()
{
// 동일 스레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK);
if (lockThreadId == (Thread.CurrentThread.ManagedThreadId << 16))
{
_writeCount++;
return;
}
// 동일 스레드가 아닌 다른 누구도 WriteLock 또는 ReadLock을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
// 멀티 스레드 환경에서 비교와 대입을 한번에 처리
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
{
_writeCount = 1;
return;
}
}
Thread.Yield();
}
}
public void WriteUnlock()
{
// 선행 연산자를 통해 우선 _writeCount를 1 감소,
// lockCount가 0이면 아무도 WriteLock을 사용하지 않으므로 WriteUnlock
// _flag를 빈 플래그로 초기화
int lockCount = --_writeCount;
if(lockCount == 0)
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
// 동일 스레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK);
if (lockThreadId == (Thread.CurrentThread.ManagedThreadId << 16))
{
Interlocked.Increment(ref _flag);
return;
}
// 아무도 WriteLock을 획득하고 있지 않으면 ReadCount를 1증가
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
int expected = (_flag & READ_MASK);
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
이제 실제 실행을 해보겠습니다.
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 < 10000; i++)
{
_lock.WriteLock();
count++;
_lock.WriteUnlock();
}
});
Task t2 = new Task(delegate ()
{
for (int i = 0; i < 10000; i++)
{
_lock.WriteLock();
count--;
_lock.WriteUnlock();
}
});
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(count);
}
}
이제 2개의 Task를 생성하여 구현한 WriterLock이 잘 동작하는지 확인해보겠습니다.
실행결과는 다음과 같습니다.
문제없이 잘 실행되는 것을 확인할 수 있습니다.
728x90
반응형
'C#' 카테고리의 다른 글
네트워크 기초 + 통신 모델 (0) | 2024.12.11 |
---|---|
Thread Local Storage (0) | 2024.12.09 |
Lock 구현 훑어내리기 + ReaderWriterLock 기초 (0) | 2024.12.04 |
Event를 통한 Lock 구현 (0) | 2024.12.04 |
Context Switching (0) | 2024.12.04 |