using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
// 재귀적 lock 허용 x
// spin lock(5000 -> yeild)
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;
public void WriteLock()
{
// 아무도 ReadLock과 WriteLock을 획득하지 있지 않을 때, 경합하여 소유권을 얻는다.
// ReadLock과 WriteLock이 안걸려 있음을 확인하는 동시에 lock을 걸어야함 -> Interlocked
// Writelock을 얻으면 ReadLock은 걸 수 없게됨
// 스레드 아이디를 16비트 만큼 밀어버림 (ReadCount에 저장되면 안되니까)
// 후에 WRITE_MASK로 WriteThreadId 부분을 제외한 비트를 밀어버려서 저장시킴
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)
return;
}
Thread.Yield();
}
}
public void WriteUnlock()
{
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
while (true)
{
for(int i = 0; i < MAX_SPIN_COUNT; i++)
{
// 기대값 = ReadCount 부분만 가져옴
int expected = (_flag & READ_MASK);
// 만약 flag가 WriteThreadId가 0이고(READ_MASK해서 WriteThreadId 부분 날림)
// expected 값과 같다면 expected + 1 을 flag에 넣음 (즉 ReadCount 증가)
// 만약 A,B가 동시에 접근하여 경합이 일어났을 때 A가 먼저 성공한다면 B의 expected값이 A에 의해
// 변경되는 일이 일어날 수 있는 데 이러한 경우 반복문을 통해 리트라이해서 성공시킬 때 까지 반복함
// lock-free 기초
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
// 재귀적 lock 허용 o , WriteLock -> WriteLock , WriteLock -> ReadLock ok , ReadLock -> WriteLock x
// spin lock(5000 -> yeild)
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;
public void WriteLock()
{
// 동일 스레드가 WriteLock을 이미 획득하고 있는지 확인
int _lockThreadId = (_flag & WRITE_MASK) >> 16;
if(Thread.CurrentThread.ManagedThreadId == _lockThreadId)
{
_writeCount++;
return;
}
// 아무도 ReadLock과 WriteLock을 획득하지 있지 않을 때, 경합하여 소유권을 얻는다.
// ReadLock과 WriteLock이 안걸려 있음을 확인하는 동시에 lock을 걸어야함 -> Interlocked
// Writelock을 얻으면 ReadLock은 걸 수 없게됨
// 스레드 아이디를 16비트 만큼 밀어버림 (ReadCount에 저장되면 안되니까)
// 후에 WRITE_MASK로 WriteThreadId 부분을 제외한 비트를 밀어버려서 저장시킴
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()
{
int lockCount = --_writeCount;
if(lockCount == 0)
Interlocked.Exchange(ref _flag, EMPTY_FLAG);
}
public void ReadLock()
{
int _lockThreadId = (_flag & WRITE_MASK) >> 16;
if (Thread.CurrentThread.ManagedThreadId == _lockThreadId)
{
Interlocked.Increment(ref _flag);
return;
}
while (true)
{
for(int i = 0; i < MAX_SPIN_COUNT; i++)
{
// 기대값 = ReadCount 부분만 가져옴
int expected = (_flag & READ_MASK);
// 만약 flag가 WriteThreadId가 0이고(READ_MASK해서 WriteThreadId 부분 날림)
// expected 값과 같다면 expected + 1 을 flag에 넣음 (즉 ReadCount 증가)
// 만약 A,B가 동시에 접근하여 경합이 일어났을 때 A가 먼저 성공한다면 B의 expected값이 A에 의해
// 변경되는 일이 일어날 수 있는 데 이러한 경우 반복문을 통해 리트라이해서 성공시킬 때 까지 반복함
// lock-free 기초
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
Thread.Yield();
}
}
public void ReadUnlock()
{
Interlocked.Decrement(ref _flag);
}
}
}
이건 더 공부해서 다시쓰기
'C# > 네트워크 관련' 카테고리의 다른 글
Socket 연결 실습 (0) | 2024.09.29 |
---|---|
Thread Local Storage 예시 (1) | 2024.09.23 |
ReaderWriterLock 예제 (0) | 2024.09.20 |
SpinLock 예제 (1) | 2024.09.15 |
Deadlock 예시 코드 (0) | 2024.09.12 |