우린 앞서 3가지의 Lock구현 방식을 배웠다. 실제 라이브러리에서 lock을 구현하는 방식을보면 한 가지의 lock방법만 고수하지않고 단점을 보완하기 위해 서로다른 lock을 조합시키는 방식으로 구현한다.
ex) 구현되어있는 Spinlock을 사용할때 실제로 spinlock처럼 누가 이미점유하고있으면 대기하긴하지만 이 시간이 너무 길어지면 yield하는 방식으로 바꾼다.
이런 정말 다앙한 방식으로 구현하면서 따라오는건 "상호배"라는 속성이다. 즉,한 스레드가 접근중이라면 다른 스레드들은 접근하지 못한다는 것이다.
하지만 이런 방식이 조금 비효율적일수도있는게 단순 읽어오는 작업은 값을 변경시키지 않기때문에 여러 스레드가 접근해도 문제가없다. 위의 방식처럼 상호배타의 원리를 적용해버리면 읽어오는 작업도 불필요하게 대기해야하는 단점이 생겨버린다.
그렇기때문에 이러한 문제를 보안한 ReaderWriterLock이라는것을 제시한다
이것은 여러 스레드가 Read에 시도하는것은 괜찮지만 어느 한스레드라도 write에 접근하면 즉시 read를 차단해버리는 식으로 구현한다.
두 가지 가정을 먼저하고
1.재귀적 락을 허용하지않음
2.스핀락으로 구현하고5000번 이상스핀하면 yield
우선 비트 플래그를 먼저 정해보자
32비트에서 [Unused(1)][WriteThreadID(15)][ReadCount(16)]이런식으로 구성하고 최상위 비트는 부호 표시이기때문에 사용하지 않는다. 다음 15비트는 만약 재귀적 락을 허용했을때 wrtie 락을 잡은 스레드 id를 표기하기 위함이다.
ReadCount는 몇개의 스레드가 읽기를 하고있는지를 기록한다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
internal class Lock
{
//비트 플래그
const int EMPTY_FLAG = 0x00000000;
const int WRITE_MASK = 0x7FFFF000;
const int READ_MASK = 0x0000FFFF;
const int MAX_SPIN_COUNT = 5000;
int _flag = 0x00000000;
}
}
writelock부터 구해보자
우선 desired즉 writelock을 잡을 스레드 id를 구해서 다른 스레드들과 경합에서 이길시 _flag에 넣어준다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;이런식으로 만든이유는 16비트로 시프트후 and연산으로 [Unused(1)] 랑 [ReadCount(16)]를 0으로 밀어버린다. ThreadId도 32비트로 구성되어있겠지만 아무리 많아도 스레드를 100이상으로 사용안할뿐더러 16비트이상 즉 2의16승인 65,536개의 스레드를 사용할 일은 전혀없기때문에 모든 스레드를 구분하기에 충분한 숫자이다.
public void WriteLock()
{
if((Thread.CurrentThread.ManagedThreadId == lockThreadID))
{
_writeCount++;
return;
}
//아무도 wrtieLock or readLock을 획득하고 있지않을때.경합해서 소유권을 얻는다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
//[Unused(1)] 랑 [ReadCount(16)]를 0으로 밀어버림
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
if (_flag == EMPTY_FLAG)
_flag = desired;
}
Thread.Yield();
}
}
하지만 위의 방식으로 구현하면, 제대로 작동되지않는다
if (_flag == EMPTY_FLAG) _flag = desired; 이부분을 원자적으로 구현하지 않았기 때문이다.
public void WriteLock()
{
//재귀를 허용하는경우 동일 스레드가 WriteLock을 이미 획득하고있는지 화인
int lockThreadID = (_flag & WRITE_MASK) >> 16;
if((Thread.CurrentThread.ManagedThreadId == lockThreadID))
{
_writeCount++;
return;
}
//아무도 wrtieLock or readLock을 획득하고 있지않을때.경합해서 소유권을 얻는다.
int desired = (Thread.CurrentThread.ManagedThreadId << 16) & WRITE_MASK;
//[Unused(1)] 랑 [ReadCount(16)]를 0으로 밀어버림
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 RaedLock()
{
//아무도 writelock을 획득하고있지않으면 readCount를 늘림
while (true)
{
for (int i = 0; i < MAX_SPIN_COUNT; i++)
{
int expected = (_flag & WRITE_MASK);
if (Interlocked.CompareExchange(ref _flag, expected + 1, expected) == expected)
return;
}
Thread.Yield();
}
}
public void RaedUnlock()
{
Interlocked.Decrement(ref _flag);
}
}'서버 공부' 카테고리의 다른 글
| [서버 공부]10. 소켓 프로그래밍 (0) | 2025.01.10 |
|---|---|
| [서버 공부]9. 네트워크 기초 + 통신모델 + 소켓프로그래밍 입문 (0) | 2025.01.09 |
| [서버 공부]7.AutoResetEvent (0) | 2025.01.07 |
| [서버 공부]6.Lock구현 - Context Switching (0) | 2025.01.03 |
| [서버 공부]5.Lock구현 - SpinLock (0) | 2025.01.02 |