본문 바로가기
게임개발/게임서버

멀티스레드 InterLocked

by do_ng 2024. 4. 18.
namespace ServerCore {
    class Program
    {
        static int number = 0;

        static void Thread_1() {
            for (int i = 0; i < 100000; i++) {
                number++;
            }
        }

        static void Thread_2() {
            for (int i = 0; i < 100000; i++) {
                number--;
            }
        }

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

1씩 100,000 번을 더하는 작업을 하는 스레드와 1씩 100,000 번을 빼는 작업을 하는 스레드가 하나의 프로세스 내에서 작업을 수행한다면 어떤 결과가 발생될까?

1씩 100,000을 먼저 더하고 그 다음에 1씩 100,000 번을 빼거나 그 반대의 경우에도 결과는 0이 나올 것으로 생각 할 수 있다. 그러나 멀티 스레드에서 결과는 다르게 발생한다.


number++; 이라는 코드는 실제 어셈블리 상에서


int temp = number;

temp = temp + 1;

number = temp; 

총 3가지 작업을 수행하게 된다.

싱글 스레드의 경우 이러한 3가지 작업이 일련의 형태로 이어지지만 멀티 스레드의 경우 3가지 작업이 무조건 순차적으로 진행된다는 보장이 없기 때문에 변수의 값이 갑자기 바뀌어서 이상한 값을 도출 할 수 있다.


Thread_A 에서 number 변수에 접근해서 1을 추가하는 작업을 하는 도중에 Thread_B로 작업이 변경되면서

Thread_A는 작업이 다 끝마치지 못한 상태로 남아있게 되고 그 사이에 변수의 값이 변경되 잘못된 값을 얻게 될 수 있다.

원자성(더 이상 쪼개질 수 없는 최소 단위)이라는 특징을 이용해서 문제를 해결 할 수 있다.

 

"Interlocked" 클래스 함수를 사용해서 여러개로 쪼개진 코드들을 쪼개질 수 없는 하나의 최소 단위로 만드는 방법)

static int number = 0;

static void Thread_1() {
    for (int i = 0; i < 100000; i++) {
        // 매개변수값으로 number 주소값을 넣어주는 이유는 무엇일까?
        // 주소값이 아니라 number 값을 가져오게 되면 값을 가져오는 순간에 이미 다른 스레드가 접근해서 수정했을 수도 있기 때문
        Interlocked.Increment(ref number); // O
        Interlocked.Increment(number); // X
    }
}

static void Thread_2() {
    for (int i = 0; i < 100000; i++) {
           // Interlocked 클래스는 값이 변경되고 그 이후의 값을 바로 반환하기 때문에 가공되지 않은 값을 얻을 수 있음
        Interlocked.Decrement(ref number);
    }
}

메이플이나 던파같은 멀티스레드 환경의 게임에서 유저끼리 물건거래를 하는 과정에 원자성이 지켜지지 않게 되는 예시로 A유저가 B유저에게 레전더리 아이템을 주고 B유저는 A유저에게 골드를 주는 순서로 거래가 이루어질때 레전더리 아이템이 B유저에게 넘어가고 나서 갑자기 서버에 문제가 일어나서 종료된다면 A유저는 골드를 받지 못하고 B유저는 꽁짜로 무기를 얻는 셈이 된다.
이러한 상황을 방지하기 위해 원자성은 매우 중요하다.

 

원자성의 단위가 실행되는 동안에는 다른 스레드가 실행되지 않게 되므로 인가되지 않은 스레드의 접근을 막을 수 있지만, 이러한 원자성을 무분별하게 사용하면 멀티 스레드의 장점을 잃게 되므로 상황에 따라서 알맞게 사용하는게 좋다.

'게임개발 > 게임서버' 카테고리의 다른 글

네트워크 기초  (0) 2024.04.22
동기화  (2) 2024.04.18
메모리 배리어  (0) 2024.04.18
멀티 쓰레드 프로그래밍  (0) 2024.04.17
멀티쓰레드 Lock 구현 방식  (2) 2024.01.15