스핀락은 그냥 공회전이라고 생각하면 된다.
특별히 하는건 없지만, 굳이 자원을 소모해가며 스레드의 주도권을 유지하는 것이다.
물론 그렇게 하는 데에는 이유가 있다. 스핀락으로 인한 낭비와 컨텍스트 스위치로 인한 낭비를 비교했을 때 전자가 더 저렴한 경우가 존재하기 때문이다.
예를 들어, 임계 구역에서의 작업이 단시간에 끝나는 경우 모니터락보다 스핀락이 더 효율적일 수 있다.
C#에는 이미 내장된 SpinLock 클래스가 존재하지만, 뭐든지 몸소 체득하는 편이 빠르므로 스핀락을 직접 구현해보기로 하였다.
스핀락의 대기는 무한루프로 이루어진다. 따라서 코드를 아래와 같이 작성할 수 있다.
class MySpinLock
{
int flag = 0; //0 = unlocked, 1 = locked
public void Enter()
{
while (flag == 1); //while locked, do nothing
flag = 1;
}
public void Exit()
{
flag = 0;
}
}
위 코드는 굉장히 단순하지만, 아쉽게도 정상 작동하는 코드는 아니다.
두 스레드가 while문을 동시에 통과할 경우 경쟁 상태가 발생할 수 있기 때문이다.
이를 해결하기 위해서는 CAS(Compare And Swap)라는 원자적 연산을 통해 flag를 체크하고 변경하는 두 작업을 단일 작업으로 만들어야 한다.
class MySpinLock
{
int flag = 0; //0 = unlocked, 1 = locked
public void Enter()
{
while (Interlocked.CompareExchange(ref flag, 1, 0) == 1); //CAS
}
public void Exit()
{
flag = 0;
}
}
C#에서 CAS에 대응하는 메서드는 Interlocked.CompareExchange이다.
이를 활용하면 flag가 0인지 체크하고, 또 1로 변경하는 작업을 원자적으로 수행할 수 있다. flag가 1일 경우 변경하지 않는다.
CAS의 반환값은 flag의 기존값으로, 이는 lock 상태인지 아닌지를 확인하는 데에 사용된다.
메서드에 관한 자세한 설명은 아래 문서에 나와있다.
아래는 검증을 위한 코드이다.
class Program
{
static int count = 0;
static MySpinLock mySplnLock = new();
static void Add()
{
for (int i = 0; i < 100000; i++)
{
mySplnLock.Enter();
count++;
mySplnLock.Exit();
}
}
static void Sub()
{
for (int i = 0; i < 100000; i++)
{
mySplnLock.Enter();
count--;
mySplnLock.Exit();
}
}
static void Main()
{
Thread t1 = new(Add);
Thread t2 = new(Sub);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine(count);
}
}
정상적인 결과가 나오는 것을 확인할 수 있다.
'CS > 운영체제' 카테고리의 다른 글
임계 영역의 문제 해결 (1) (0) | 2022.09.08 |
---|---|
경쟁 상태와 임계 영역(Critical section) (0) | 2022.09.06 |
경쟁 상태(Race condition)의 간단한 예시 (0) | 2022.08.25 |
FCFS, SJF, SRTF, RR의 계산 및 비교 (0) | 2022.08.23 |
Context switch란 (0) | 2022.08.19 |
댓글