namespace ServerCore;
// 재귀적 락을 허용 (Yes) WriteLock -> WriteLock OK, WriteLock -> ReadLock OK, ReadLock -> WriteLock No
// 스핀락 정책 (5000번 -> Yield)
public class Lock
{
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;
// [Unused(1)] [WriteThreadId(15)] [ReadCount(16)]
private int _flag;
private int _writeCount = 0;
public void WriteLock()
{
// 동일 쓰레드가 WriteLock을 이미 획득하고 있는지 확인
int lockThreadId = (_flag & WRITE_MASK) >> 16;
if (Thread.CurrentThread.ManagedThreadId == lockThreadId)
{
_writeCount++;
return;
}
// 아무도 WriteLock or ReadLock 을 획득하고 있지 않을 때, 경합해서 소유권을 얻는다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
// 시도를 해서 성공하면 return
if (Interlocked.CompareExchange(ref _flag, desired, EMPTY_FLAG) == EMPTY_FLAG)
{
_writeCount = 1;
return;
}
}
Thread.Yield();
}
}
public void WriteUnlock()
{
int lockCount = --_writeCount;
if (lockCount == 0)
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
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++)
{
// 만약 WriteThreadId 가 0 이 아닌 경우에는 무조건 fail 임
// writeLock 을 잡았다 = _flag의 WriteThreadId가 0 이 아님.
// 밑에서 비교하면 무조건 fail
int expected = (_flag & READ_MASK);
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
// if ((_flag & WRITE_MASK) == 0)
// {
// _flag = _flag + 1;
// return;
// }
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}