서버/서버 이론

InterLocked

잼잼재미 2025. 3. 24. 23:42

InterLocked


멀티 쓰레드에서 공유하는 변수에 대한 원자 단윈 연산을 제공하는 클래스

 

 

class Program
{
    static int number = 0;

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

    static void Thread_2()
    {
        for (int i = 0; i < 10000; 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이 출력 될 것이다.

 

 

class Program
{
    static int number = 0;

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

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

 

 

 

Race Condition(경쟁 상태)

하지만 이렇게 반복문의 크기가 더 커진다면 이렇게 의도와 다른 값이 출력 된다. 그 이유는 두 개 이상의 쓰레드가 공유 자원에 대해 읽거나 쓸 때 데이터에 대한 접근 순서로 인해 실행 결과가 달라지기 때문이다. 

 

 

Atomic (원자성)

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

 

위 코드는 사실 아래 코드와 같이 동작한다. 위 코드 한줄이 두개의 쓰레드에서 무작위로 실행되는 것이 아니라, 아래 코드 세줄이 두개의 쓰레드에서 무작위로 실행된다는 의미다. 그래서 쓰레드의 접근 순서를 보장받지 못하면 의도하지 못한 값이 나올 수 있다. 

원자성이란 더 이상 나눌수 없는 최소 단위다. 경쟁상태를 피하기 위해서는 공유자원에 대한 처리나 연산의 원자성을 지켜야 한다.

 

ex) 게임 상점에서 검을 100원에 사는 행위는 원자성을 지켜서 동시에 일어나도록 해야한다.

 

 

반복문 10000회는 되고, 반복문 1000000회는 된다?

10000회는 비교적 횟수가 적기 때문에, 확률적으로 쓰레드 교차가 발생하지 않을 가능성이 높음!

 

 

 

Interlocked 사용


class Program
{
    static int number = 0;

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

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

 

위와 같이 Interlocked로 1을 더하거나 1을 빼는 연산을 할 수 있다. 이렇게 Interlocked를 사용하면, 아까 3줄의 코드가 한번에 실행되면서 쓰레드의 실행 순서를 보장받게 된다. (원자성을 보장)

 

* 메모리 배리어가 간접적으로 작동하기 때문에 따로 사용하지 않아도 된다.

* Interlocked.Increment(ref number); 가 실행된다면, 실행이 완전히 끝나고, Interlocked.Decrement(ref number); 가 실행된다.

* Interlocked는 부담이 많이 가는 작업으로, 일반적인 경우보다 느리다.

'서버 > 서버 이론' 카테고리의 다른 글

데드락 (DeadLock)  (0) 2025.03.28
Lock  (0) 2025.03.27
메모리 배리어  (0) 2025.03.19
캐시 이론  (0) 2025.03.18
쓰레드 (Thread)  (0) 2025.03.03