본문 바로가기
공부/게임서버

Interlocked 사용 시 그 반환값을 사용하자

by samosa 2024. 11. 9.

싱글 스레드 조건에서 가졌던 마인드는 버리자!

무슨 말이냐. 아래 코드는 문제가 있다는 것이다.

namespace ServerCore;

internal class Program
{
    private static int number;

    private static void Thread1()
    {
        for (var i = 0; i < 1000000; i++)
        {
            var a = number;
            Interlocked.Increment(ref number);
            var b = number;

            Console.WriteLine($"{a} -> {b}");
        }
    }

    private static void Thread2()
    {
        for (var i = 0; i < 1000000; i++)
            Interlocked.Decrement(ref number);
    }

    private static void Main(string[] args)
    {
        var t1 = new Task(Thread1);
        var t2 = new Task(Thread2);

        t1.Start();
        t2.Start();

        Task.WaitAll(t1, t2);

        Console.WriteLine(number);
    }
}

 

number를 저런 식으로 빼는 순간, 다시 경합 조건이 발생해서 정확하지 않은 값을 읽을 수 있다는 것.

애초에 저렇게 값을 복사하는 일은 없어야 한다.

 

문제 해결의 핵심은 number 변수에 대한 동시 접근 시 정확성을 보장하기 위해 Interlocked의 반환값을 사용하는 것이다. Interlocked 메서드를 사용할 때는 반환값을 사용해야 정확한 결과를 얻을 수 있다.

문제점 요약

  • numbervar a = number;로 읽을 때, 다른 스레드가 값을 변경할 수 있어 정확하지 않다.
  • Interlocked.IncrementInterlocked.Decrement의 반환값을 사용하지 않으면, 원자적 연산 결과를 놓칠 수 있다.

수정된 코드

number의 증감 연산 후 값을 정확히 출력하려면 Interlocked의 반환값을 변수에 저장해야 한다.

namespace ServerCore
{
    internal class Program
    {
        private static int number;

        private static void Thread1()
        {
            for (var i = 0; i < 1000000; i++)
            {
                var b = Interlocked.Increment(ref number); // 반환값을 사용하여 정확한 값 보장
                Console.WriteLine($"Incremented to {b}");
            }
        }

        private static void Thread2()
        {
            for (var i = 0; i < 1000000; i++)
            {
                var b = Interlocked.Decrement(ref number); // 반환값을 사용하여 정확한 값 보장
                Console.WriteLine($"Decremented to {b}");
            }
        }

        private static void Main(string[] args)
        {
            var t1 = new Task(Thread1);
            var t2 = new Task(Thread2);

            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine($"Final value: {number}");
        }
    }
}

 

  • Interlocked.IncrementInterlocked.Decrement의 반환값을 바로 사용하여 연산 후 정확한 값을 출력한다.
  • 이로써 다중 스레드 환경에서도 정확한 결과를 보장할 수 있다.