📄 스핀락
스핀락은 잠금을 획득하려는 스레드가 특정 코드가 현재 사용 가능한지 반복적으로 확인하면서 단순히 루프('스핀')에서 기다리는 것입니다. 스레드는 활성 상태를 유지하지만 계속 락에 대한 검사를 수행하는 형태를 띱니다.
장점:
spinning 중이고 스레드가 휴지 상태가 아니기 때문에 콘텍스트 전환이 필요하지 않습니다.
크리티컬 섹션(스레드가 동시에 접근해서는 안 되는 공유 자원을 접근하는 코드)이 작으면 도움이 됩니다.
단점:
Spinlock은 spinning이 필요합니다.
락 상태에서 사용할 수 없으면 액세스 가능한지 반복적으로 확인합니다.(CPU를 낭비)
📑 SpinLock 이론
namespace ServerCore
{
class SpinLock
{
private volatile bool mLocked = false;
public void Acquire()
{
while(mLocked)
{
//잠김이 풀리기를 기다림
}
mLocked = true;
}
public void Release()
{
mLocked = false;
}
}
class Program
{
static int number = 0;
static SpinLock spinLock = new SpinLock();
static void Thread1()
{
for(int i = 0; i < 100000; ++i)
{
spinLock.Acquire();
number++;
spinLock.Release();
}
}
static void Thread2()
{
for (int i = 0; i < 100000; ++i)
{
spinLock.Acquire();
number--;
spinLock.Release();
}
}
static void Main(string[] args)
{
Task task1 = new Task(Thread1);
Task task2 = new Task(Thread2);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
Console.WriteLine(number);
}
}
}
- 스핀락을 간단히 구현한 형태입니다.
- 단순하게 접근하여 bool변수가 true(locked)이면, while문을 계속 돌며 locked가 false가 될 때까지 기다립니다.
- locked가 false가 되면 locked를 true로 바꾸고, 자신의 코드를 수행하고 다시 false로 바꾸는 것을 반복하는 구조입니다.
- 예상과 달리 0이 나오지 않고 예측 불가능한 값이 나왔습니다.
- mLocked가 false인지 확인하고, true로 바꾸는 과정에서 원자성이 보장되지 않은 코드이기에 발생하는 문제입니다.
- 원자성을 보장하는 기능을 포함하여 스핀락을 구현해야합니다.
📑 SpinLock 1
namespace ServerCore
{
class SpinLock
{
private volatile int mLocked = 0; //0: Unlocked, 1: Locked
public void Acquire()
{
while(true)
{
//리턴값은 mLocked을 넣기 전의 값(prev value)이고, 1로 변경하고자 함
//리턴값이 이미 1이었다면, 다른 스레드에서 이미 잠근상태에서 사용중이라는 뜻으로 획득 불가
int original = Interlocked.Exchange(ref mLocked, 1);
//만약 리턴값이 0이라면, mLocked는 0, 아직 잠기지 않은 상태이므로 획득 가능
if (original == 0) { break; }
}
}
public void Release()
{
mLocked = 0;
}
}
class Program
{
static int number = 0;
static SpinLock spinLock = new SpinLock();
static void Thread1()
{
for(int i = 0; i < 1000000; ++i)
{
spinLock.Acquire();
number++;
spinLock.Release();
}
}
static void Thread2()
{
for (int i = 0; i < 1000000; ++i)
{
spinLock.Acquire();
number--;
spinLock.Release();
}
}
static void Main(string[] args)
{
Task task1 = new Task(Thread1);
Task task2 = new Task(Thread2);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
//예상되는 출력은 0
Console.WriteLine(number);
}
}
}
- Interlocked 기능을 사용한 방법입니다.
int original = Interlocked.Exchange(ref mLocked, 1);
- Interlocked.Exchange 함수를 사용하여 현재 mLocked의 값이 두 번째 매개변수인 {1}로 바뀌며, 리턴값은 바꾸기 이전의 값입니다.
- 리턴값은 mLocked을 넣기 전의 값으로 나온 상태로, 만약 값이 0이라면 아직 잠기지 않은 상태로 사용이 가능합니다.
- 리턴값이 1이라면 다른 스레드에서 이미 잠근상태에서 사용 중입니다.
📑 SpinLock 2
namespace ServerCore
{
class SpinLock
{
private volatile int mLocked = 0; //0: Unlocked, 1: Locked
public void Acquire()
{
while(true)
{
/*
매개 변수
location1 comparand 값과 비교되어 value로 바뀔 수 있는 값을 가진 대상입니다.
value 비교 결과가 같은 경우 대상 값을 바꿀 값입니다.
comparand location1의 값과 비교할 대상입니다.
반환
location1의 원래 값입니다.
*/
//리턴값이 0일경우, 언락 상태기에 1로 바꿔 다른스레드에서 사용할 수 없게 한다
//리턴값이 1인경우, 이미 언락 상태이기에 값은 변하지 않음
int expected = 0; //잠겨져있지 않을때 나올 값
int desired = 1; //잠궜을때 설정할 값
if (Interlocked.CompareExchange(ref mLocked, desired, expected) == expected) { break; }
}
}
public void Release()
{
mLocked = 0;
}
}
class Program
{
static int number = 0;
static SpinLock spinLock = new SpinLock();
static void Thread1()
{
for(int i = 0; i < 1000000; ++i)
{
spinLock.Acquire();
number++;
spinLock.Release();
}
}
static void Thread2()
{
for (int i = 0; i < 1000000; ++i)
{
spinLock.Acquire();
number--;
spinLock.Release();
}
}
static void Main(string[] args)
{
Task task1 = new Task(Thread1);
Task task2 = new Task(Thread2);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
//예상되는 출력은 0
Console.WriteLine(number);
}
}
}
- 위 방법과 같은 원리로 동작하는 다른 함수입니다.
- 리턴값은 동일하게 mLocked의 원래 값입니다.
- desired는 mLocked와 expected를 비교하여 값이 같으면 mLocked를 바꿀 값입니다.
- expected는 mLocked와 비교할 값입니다.
- 리턴값이 0이라면, 함수를 호출할 때 잠기지 않은 상태이고, expected와 같기 때문에 mLocked는 1로 바뀌었습니다.
- 다른 스레드가 같은 함수를 호출하면 이미 1로 바뀌어있기 때문에 잠긴 상태가 되어 다음 단계로 넘어갈 수 없습니다.
'server > socket server' 카테고리의 다른 글
[C# 서버] AutoResetEvent (1) | 2023.01.06 |
---|---|
[C# 서버] Context Switching (0) | 2023.01.04 |
[C# 서버] 원자성, Interlocked, Monitor, lock (0) | 2023.01.02 |
[C# 서버] 메모리 배리어 (0) | 2023.01.02 |
[C# 서버] volatile (0) | 2022.12.30 |