스핀 락(SpinLock)
이론 -
락이 잠겨 있을 때 락이 풀릴 때 까지 뺑뺑 돌면서 대기.
코드구현 1.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class SpinLock
{
volatile bool _lock = false;
public void Acquire()
{
while (_lock)
{
}
_lock = true;
}
public void Release()
{
_lock = false;
}
}
class Program
{
static int _num = 0;
static SpinLock _lock = new SpinLock();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_num++;
_lock.Release();
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.Acquire();
_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 값 : {_num}");
}
}
}
설명. Acquire - 습득하다. Release - 풀어주다.
Acquire 설명.
lock 이 True 가 될 때 까지 무한정 루프를 띄고 혹여나 lock이 true가 될 경우 자신이 true로 가져간다.
상세설명. 누군가가 Lock을 잠금 한 상태라면 Lock이 True가 되어있을테니 무한정 루프를 돌다가 이후 Lock 어디에선가
false로 바꿔주면 다시 자신이 가져간다는 의미로 True로 바꿔주는것이다.
Release 설명.
볼 일을 다보고 잠금 상태를 다시 해제 해주는 작업. Lock 을 False로 바꿔준다.
상세설명. 먼저 일을 시작한 사람이 잠금을 풀고 나가면 다른 이가 들어와 볼일을 볼 수 있게 Lock을 풀어주는 작업이다.
결과 설명.
아마 위 코드대로 작업을 했다면 num의 값이 랜덤으로 돌아갈 것이며 우리가 원했던 SpinLock이 제대로 구현되지 않았다. 그 이유는 양쪽에서 동시다발적으로 어마어마하게 빠른 속도로 경합을 해서 둘 다 일을 하게 되는 상태. 한쪽 짜리 방안에 두 명의 사람이 들어가버려 꼬인 상태.
class SpinLock
{
volatile int _locked = 0;
public void Acquire()
{
while (true)
{
//방법1.
int original = Interlocked.Exchange(ref _locked, 1);
if (original == 0)
break;
}
}
public void Release()
{
_locked = 0;
}
}
설명. 해당 부분을 해결하기 위해선 2가지 방법을 사용할 수 있다. 바로 인터락 함수를 사용하여 해결한다. 인터락 함수에 대한 자세한 설명은 생략하되 간단히 설명해보자면 원자성(Atomicity) - 절대로 쪼개어질 수 없는걸 뜻 하는데. 이게 왜 필요하냐? 예를들어 우리가 num에 ++을 한다고 가정해보자.
원자성에 대한 예시.
num++;
그러면 우리 눈에는 한줄로 보이지만 하드웨어상 최적화를 위해 해당 코드가 아래와 같이 바뀌게 된다.
int temp = num;
temp++;
temp = num;
즉 이러한 일렬의 과정에서 멀티쓰레드 환경에서는 경합조건이 발생되어 꼬이게 될 수 있기 때문에 우리는 interlocked함수를 이용해서 아래와 같이 작업을 해줄땐.
Interlocked.Increment(ref num) 혹은 Interlocked.Decrement(ref num) 를 사용해주었다. 해당 코드들은 곧바로 ref(주소)에 해당하는 변수에 1을 덧셈(increment) 빼기(Decrement)를 해주었다.
어쨌든 중요한건 Interlocked.Exchange(ref _locked, 1)를 살펴보자면, Exchange 라는 함수인데 해당 함수의 핵심은
int로 반환 해준다는 것인데 일단 간단히 _locked라는 변수에 1을 덧셈해주면서 덧셈하기 이전의 값을 int로 반환해준다는 함수이다.
쉽게 말해 Original이라는 변수에는 _locked에 1이 덧셈되기전의 숫자가 출력되는 것이며, _locked는 1이 Increment함수처럼 원자성으로 덧셈된 상태이다.
int exected = 0; // 예상한값이 무엇이냐
int desired = 1; // 원하는 값은 무엇이냐
if (interlocked.compareexchange(ref _locked, desired, exected) == exected)
break;
또 다른 방법은 compareexchange 함수를 사용할 수 있다.
마치며. 해당 글은 본인의 복습 메모장 용도이기에. 자세한 내용과 설명은 직접 강의를 참고해주시길 바랍니다.