C#

Interlocked

monstro 2024. 11. 27. 17:58
728x90
반응형

멀티스레드 환경은 싱글스레드와 다르게 신경써야 하는 것이 매우 많습니다.

그 중에서도 전역 변수같은 공용으로 사용하는 변수에 대해서 알아보겠습니다.

 

아래의 코드를 한번 살펴보겠습니다.

class Program
{
    static int number = 0;

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

    static void Thread_2()
    {
        for (int i = 0; i < 100000000; 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);
    }
}

 

일반적으로 생각하는 결과는 0일 것입니다.

100000000번동안 1을 더하고, 100000000번동안 1을 빼므로 값은 0이라 생각되지만, 결과는 다음과 같습니다.

 

위와 같은 결과가 발생한 이유는 공용변수 Task에 대한 원자성이 보장받지 못하기 때문입니다.

간단하게 말하자면 원자성이 보장받는다는 것은

해당 데이터에 수행하는 작업이 동시에 발생할 수 있는 작업과 분리되어 있는 경우라고 얘기할 수 있습니다.

즉, 원자성이 보장되어 있다면 하나의 연산만이 이뤄지게 되고

다른 연산으로 분리할 수 없습니다.

 

Task에 대해 원자성을 보장해야 여러 스레드가 달려들어 여러 작업을 처리하는 것을 방지할 수 있습니다.

이를 위해 Interlocked를 사용하여 코드를 다음과 같이 수정해보겠습니다.

Interlocked 에서 제공되는 함수를 사용하면 변수를 사용할 때 CPU 단에서 원자성을 보장받을 수 있습니다.

class Program
{
    static int number = 0;

    static void Thread_1()
    {
        for (int i = 0; i < 100000000; i++)
            Interlocked.Increment(ref number);
    }

    static void Thread_2()
    {
        for (int i = 0; i < 100000000; i++)
            Interlocked.Decrement(ref 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);
    }
}

 

실행결과는 다음과 같습니다.

 

Task에 대한 원자성이 보장받게끔 연산이 이뤄져 값이 의도한 대로 0이 나오는 것을 확인할 수 있습니다.

Interlocked를 사용하면 멀티 스레드에서 데이터를 안전하게 조작할 수 있지만,

해당 데이터를 사용하는 모든 작업에서 작업속도를 늦추므로 유의할 필요가 있습니다.

728x90
반응형

'C#' 카테고리의 다른 글

DeadLock과 Lock구현  (0) 2024.12.02
Lock 기초  (1) 2024.12.02
Memory Barrier  (0) 2024.11.27
캐싱  (1) 2024.11.27
컴파일러 최적화  (0) 2024.11.25