독학백수 2021. 1. 26. 15:37
반응형

이론 - 

락이 잠겨 있을 때 락이 풀릴 때 까지 뺑뺑 돌면서 대기. 

코드구현 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 설명.

lockTrue 가 될 때 까지 무한정 루프를 띄고 혹여나 locktrue가 될 경우 자신이 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 함수를 사용할 수 있다. 

 


마치며. 해당 글은 본인의 복습 메모장 용도이기에. 자세한 내용과 설명은 직접 강의를 참고해주시길 바랍니다. 

반응형