Lock 이론에대해 공부해봤으니 대표적인 Lock을 구현하는 방식인 SpinLock을 구현해보고자한다.
간단하게 _locked변수를 스레드중에 먼저 true시킨다면 다른스레드는 무한 대기하도록 구현하고 실행해보면
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
volatile bool _locked = false;
public void Aquire()
{
//lock이 풀릴때까지 대기
while (_locked)
{
}
//가져옴
_locked = true;
}
public void Release()
{
_locked = false;
}
}
internal class Program
{
static int num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2= new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1,t2);
Console.WriteLine(num);
}
}
}

의도한대로 값이 나오지 않는다. 이것은 전에 설명했던 원자성과 관련이있는데,
1. _locked가 false인지 확인
2. false라면 _locked를 true로 변경 -> 검사하고 true를 대입하는 과정이 원자적으로 이루어지지 않음
그렇기때문에 동시에 _locked을 true로 만들어버리는 경우가 생겨 의도한 결과 값이 나오지 않은것이다.
이것을 해결하기 위해 전에 배웠던 Interlock을 사용한다고 하면, 정수를 이용해야하니 0은 false 1은 true로 사용하자
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
volatile int _locked = 0;
public void Aquire()
{
while (true)
{
//original은 이전값
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0)
break;
}
}
public void Release()
{
_locked = 0;
}
}
internal class Program
{
static int num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2= new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1,t2);
Console.WriteLine(num);
}
}
}
Exchange라는 함수를 통해이러한 절차를 원자적으로 해결할수있다.
여기서 조금 개선된 방법은 무작정 _locked에 1을 대입하고있기때문에 이러한 방식은 조금 위험해질수있으니 비교후 넣어주는 함수인 CompareExchagne를 사용하도록하자
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
volatile int _locked = 0;
public void Aquire()
{
while (true)
{
//CAS Compare and Swap
int expected = 0;
int desire = 1;
if(Interlocked.CompareExchange(ref _locked, desire, expected) == expected)
break;
}
}
public void Release()
{
_locked = 0;
}
}
internal class Program
{
static int num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Aquire();
num--;
_lock.Release();
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread_1);
Task t2= new Task(Thread_2);
t1.Start();
t2.Start();
Task.WaitAll(t1,t2);
Console.WriteLine(num);
}
}
}

'서버 공부' 카테고리의 다른 글
| [서버 공부]7.AutoResetEvent (0) | 2025.01.07 |
|---|---|
| [서버 공부]6.Lock구현 - Context Switching (0) | 2025.01.03 |
| [서버 공부]4. Lock (0) | 2025.01.02 |
| [서버 공부]3.메모리 배리어 + Interlocked (0) | 2025.01.01 |
| [서버 공부]2.컴파일 최적화 + 캐시 (0) | 2025.01.01 |