서버 공부

[서버 공부]5.Lock구현 - SpinLock

myjeongjun 2025. 1. 2. 23:36

 

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

 

 

출력시 의도한대로 나온 모습