본문 바로가기
CS/운영체제

C#으로 구현한 스핀락

by alpacadabra 2022. 11. 22.

스핀락은 그냥 공회전이라고 생각하면 된다.

특별히 하는건 없지만, 굳이 자원을 소모해가며 스레드의 주도권을 유지하는 것이다.

물론 그렇게 하는 데에는 이유가 있다. 스핀락으로 인한 낭비와 컨텍스트 스위치로 인한 낭비를 비교했을 때 전자가 더 저렴한 경우가 존재하기 때문이다.

예를 들어, 임계 구역에서의 작업이 단시간에 끝나는 경우 모니터락보다 스핀락이 더 효율적일 수 있다.

 

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 상태인지 아닌지를 확인하는 데에 사용된다.

 

메서드에 관한 자세한 설명은 아래 문서에 나와있다.

 

 

Interlocked.CompareExchange 메서드 (System.Threading)

두 값이 같은지 비교하여 같으면 첫 번째 값을 바꿉니다.

learn.microsoft.com

 


 

아래는 검증을 위한 코드이다.

 

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);
    }
}

결과

정상적인 결과가 나오는 것을 확인할 수 있다.

댓글