728x90
이 글은 Inflearn - Rookiss : 게임서버 강의를 듣고 정리한 글입니다.

 

목차.

  1. 개요
  2. Interlocked.CompareExchage 활용
  3. 정리

 

개요

 

이번 글에서 Lock을 이용할 때 멀티 스레드로 인해 원하는 결과가 나오지 않는 상황과 해결하는 방법을 다뤄보겠습니다.

 

using System;
using System.ComponentModel;
using System.Numerics;


namespace ServerCore 
{
    class SpinLock 
    {
        volatile bool _locked = false;

        public void Acquire()
        {
            while (_locked)
            {
            
            }
            _locked = true;
        }

        public void Release() 
        {
            _locked = 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);
        }
    }
}

 

위 코드는 Acquire() 함수가 호출되면 계속해서 while 반복문이 돌다가 Release() 함수를 통해 _locked에 false가 할당되면 반복문이 멈추게 되고 다음 코드가 실행되는 방식입니다.

 

확실히 이 코드라면 루프를 도는 동안 다른 코드가 접근하지 못하기 때문에 0이라는 결과가 나와야 합니다.

실행시키면 어떤 결과가 나올까요?

 

무작위-값-출력
무작위 값 출력

0이 나올 것이라는 예상과는 달리 무작위 값이 출력됩니다.

 

이러한 결과의 원인은 다음과 같습니다.

 

문제의-원인
문제의 원인

멀티 스레드로 인해 하나의 스레드만 Acquire에 접근한 게 아니라 가끔 동시다발적으로 접근해서 발생한 문제이다.

 

그럼 어떻게 문제를 해결할 수 있을까?

 

Interlocked.CompareExchage 활용

 

using System;
using System.ComponentModel;
using System.Numerics;


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

        public void Acquire()
        {
            while (true)
            {
                int expected = 0;
                int desired = 1;
                if (Interlocked.CompareExchange(ref _locked, desired, expected) == expected)
                    break;
            }
        }

        public void Release() 
        {
            _locked = 0;
        }
    }
    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);
        }
    }
}

 

이 코드의 핵심은 CompareExchange() 함수를 사용한다는 것이다.

 

CompareExchange-함수-사용
CompareExchange함수 사용

CompareExchage 함수는 스레드 간 값을 변경하거나 교환하기 위해 사용되는 함수입니다.

이 함수를 사용하면 여러 스레드가 동시에 값을 수정하려고 할 때 발생할 수 있는 문제를 방지하고, 그 결과 값을 안전하게 수정할 수 있습니다.

 

CompareExchange 매개변수

Interlocked.CompareExchange()의 매개변수로

location : 값을 변경하려는 변수의 참조를 나타냅니다.

value : 변수에 새로 설정할 값을 나타냅니다.

comparand : 변수의 현재 값과 비교할 값을 나타냅니다.

 

 

정리

 

위에서 사용한 코드를 해석해 보자면

비교할 값은 0이고  만약 일치한다고 하면 원하는 value를 locked에 할당하겠다는 뜻이 됩니다.

0이-출력되는-모습
정상적으로 0 이 출력되는 모습

이제 정상적으로 0이 출력되는 것을 볼 수 있습니다.

 

이번 글에서 Lock을 사용했을 때 발생하는 문제를  Interlocked.CompareExchange를 사용해서 해결하는 방법과 개념에 대해서 알아봤습니다.

728x90

+ Recent posts