Read 작업은 값을 변경하지 않으니, 하나의 크리티컬 섹션에 여러개의 스레드가 진입해도 일관성 관련 문제가 발생하지 않는다.
Write 작업은 값을 변경 할 수도 있으니, 하나의 크리티컬 섹션에 여러개의 스레드가 진입 할 경우 일관성이 깨질 수 있다.
Read 작업은 여러개의 스레드가 하나의 크리티컬 섹션에 접근 가능하지만, Read하는 도중 값이 변경 되면 안된다.
출처: https://kukuta.tistory.com/35 [HardCore in Programming:티스토리]
하나의 스레드에서 다음과 같이 락을 얻는 상황이 가능하다.
Write -> Write (O),
Write -> Read (O),
Read -> Write (X)
namespace SeverCore
{
// 재귀적 락 Write->Write (O), Write->Read (O), Read->Write (X)
// 스핀락 (5000번 -> Yield)
internal class Lock
{
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0x7FFF0000;
const int READ_MASK = 0x0000FFFF;
const int MAX_SPIN_COUNT = 5000;
// [Unused(1)] [WriteThreadId(15)] [ReadCount(16])
int _flag = EMPTY_FLAG;
int _writeCount = 0;
WriteLock을 얻는 함수
public void WriteLock()
{
// 동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK) >> 16;
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
_writeCount++;
return;
}
// 아무도 WriteLock 이나 ReadLock을 획득하고 있지 않은 경우 소유권을 얻는다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
while (true)
{
for (int i=0; i<MAX_SPIN_COUNT; i++)
{
// CompareExchange를 통해 현재 _flag가 EMPTY면 락을 획득하는 것으로 볼 수 있다.
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
{
_writeCount = 1;
return;
}
}
// 5천번 돌고 안되면 양도.
Thread.Yield();
}
}
WriteLock을 Release하는 함수
public void WriteUnlock()
{
// 락을 풀면 EMPTY_FLAG로 밀어버린다.
int lockCount = --_writeCount;
if (lockCount == 0)
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
ReadLock을 얻는 함수
public void ReadLock()
{
// 동일 스레드가 ReadLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK) >> 16;
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
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();
}
}
ReadLock을 Release하는 함수
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}