C#/네트워크 관련

SpinLock 예제

tmd1 2024. 9. 15. 23:18
SpinLock 구현 #1
namespace ServerCore
{
    class SpinLock
    {
        volatile bool _locked = false;

        public void Acquire()
        {
            
            while (_locked)
            {
                // lock 풀릴때 까지 무한반복, 즉 남의 lock을 기다림
            }

            // lock 획득
            _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.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);




        }
    }
}

 

스레드 1,2가 Acquire을 동시에 실행하여 누가 먼저 lock을 걸지 않은 상태에서 while문을 통과한다면 문제가 됨.

 

둘다 동시에 lock을 거는 상황이 생기기 때문

 

 

SpinLock 구현 #2
namespace ServerCore
{
    class SpinLock
    {
        volatile int _locked = 0;

        public void Acquire()
        {
            
            while (true)
            {   
                // Interlocked.Exchange는 값을 바꾸기 이전 원본 값(original)을 반환
                // _locked를 1로 바꿈(잠금) -> _lock의 원본 값이 0이라면(잠김 풀림 상태)
                // 잠금이 풀릴 때 까지 대기할 이유가 없으므로 바로 while문을 빠져나옴
                // _lokced의 원본 값이 1이라면(잠긴상태) _locked의 원본 값이 0으로 바뀔 떄 까지(_lock 풀림) 기다림
                // _locked의 원본 값은 Realease에 의해 변경됨.
                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.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);




        }
    }
}

 

lock을 먼저 걸어버림으로써 해결

 

namespace ServerCore
{
    class SpinLock
    {
        volatile int _locked = 0;

        public void Acquire()
        {
            
            while (true)
            {
                int desired = 1; // lock을 걸길 원함
                int expected = 0; // lock이 안걸려 있어야 함

                // _locked가 만약 expected라면 desired 값을 넣고 원본 값을 반환
                // _locked가 만약 expected와 다르다면 값을 넣지않고 원본 값을 반환
                // 만약 lock이 안걸려있다면(_locked == expected) lock을 걸고(_lock = desired)
                // 만약 lock이 걸려있다면 (_locked != expected) lock이 풀릴 때 까지 대기
                // (_lock의 원본 값이 expected와 같을 때 까지 while문을 반복)
                // (_lock의 값은 Release에 의해 바뀌니 Release 즉, lock 해제를 대기)
                if (Interlocked.CompareExchange(ref _locked, desired, 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.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);




        }
    }
}

 

비교(lock이 걸려있는가?) 와 변경(lock을 걺) 을 동시에 함