using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
// _locked == true -> 잠김상태
volatile bool _locked = false;
public void Acquire()
{
// 잠김상태이면 무한루프로 풀리기를 기다린다.
while(_locked)
{
}
// 잠김이 풀렸으니 내가 사용
_locked = true;
}
public void Release()
{
_locked = false;
}
}
class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread1);
Task t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(_num);
}
}
}
SpinLock의 구현
하지만 정상작동하지 않음
public void Acquire()
{
// 잠김상태이면 무한루프로 풀리기를 기다린다.
while(_locked)
{
}
// 잠김이 풀렸으니 내가 사용
_locked = true;
}
잠김상태를 확인하고 잠겨있지 않았다면 내가 잠그는 행위가 원자적으로 이루어지지 않았기 때문에 발생하는 문제
class SpinLock
{
// _locked == true -> 잠김상태
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0) break;
}
}
public void Release()
{
_locked = 0;
}
}
Interlocked.Exchange를 사용하여 문제 해결 (_locked 변수는 bool 타입에서 int 타입으로 변경하여 사용)
original = Interlocked.Exchange(ref _locked, 1)은 _locked 변수에 1을 대입하고 기존 _locked 변수를 original에 return 해준다.
즉, 기존 _locked값이 0이었다면 잠김상태가 아니란 뜻이니 1을 대입하여 잠가주고 break를 통해 빠져나오는 것이다.
{
int original = _locked;
_locked = 1;
if (original == 0) break;
}
Acquire()의 작동방식 (Interlocked.Exchange)
public void Acquire()
{
while (true)
{
int original = Interlocked.CompareExchange(ref _locked, 1, 0);
if (original == 0) break;
}
}
Interlocked.CompareExchange를 사용하여 문제를 해결할 수도 있다.
_locked과 0이 같아면 _locked에 1을 대입해주는 방식
{
if (_locked == 0) _locked = 1;
}
Interlocked.CompareExchange의 작동방식 (CAS (Compare - And - Swap) 계열)
Thread.Sleep(1); // 무조건 휴식 => 1ms
Thread.Sleep(0); // 조건부 양보 => 나보다 우선순위가 낮은 애들한테는 양보 불가 => 우선순위가 나보다 같거나 높은 쓰레드가 없으면 다시 본인한테
Thread.Yield(); // 관대한 양보 => 관대하게 양보, 지금 실행이 가능한 쓰레드가 있으면 실행 => 실행 가능한 쓰레드가 없으면 남은시간 소진
일정 시간 뒤 다시시도를 구현하기 위해 셋중 하나를 선택하여 사용
class Program
{
static SpinLock _lock = new SpinLock();
static void Main(string[] args)
{
bool lockTaken = false;
try
{
_lock.Enter(ref lockTaken);
}
finally
{
if (lockTaken) _lock.Exit();
}
}
}
SpinLock은 이미 구현이 되어있어 그대로 사용해도 된다.
내부적으로는 계속 시도를 하지만 계속 Lock 상태라면 Yield를 가끔 주어 양보를 하기도 한다.
'네트워크' 카테고리의 다른 글
ReaderWriterLock (0) | 2023.08.04 |
---|---|
AutoResetEvent (0) | 2023.08.03 |
DeadLock (0) | 2023.08.01 |
Lock 기초 (0) | 2023.07.31 |
Interlocked (0) | 2023.07.30 |