[CS] 경쟁조건(Race Condition)

이정석·2023년 7월 26일
0

CS

목록 보기
4/7

경쟁조건

경쟁조건, 경합조건이라 불리는 RaceCondition은 멀티 쓰레드 환경에서 공유 자원에 동시에 접근하고 수정하려 할 때 쓰레드의 실행순서나 타이밍에 따라 결과가 달라지는 상황을 얘기한다.

아래 C#코드를 보면 공유 자원(numer)에 접근하고 수정하는 2개의 쓰레드(Thread1, Thread2)가 있다.

Thread1은 number를 1증가시키는 연산을, Thread2는 number를 1감소시키는 연산을 각각 10만번씩 한다.

    static int number = 0;

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

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

    static void Main(string[] args)
    {
        for (int i = 0; i < 5; ++i)
        {
            Task t1 = new Task(Thread1);
            Task t2 = new Task(Thread2);
            t1.Start();
            t2.Start();

            Task.WaitAll(t1, t2);

            Console.WriteLine(number);
        }
    }

위 코드의 결과를 예상하자면 당연히 0일 것이다. 메인쓰레드에서 t1,t2를 실행시키고 각 loop에 t1,t2를 대기하고 10만번의 증가와 10만번의 감소로 인해 0이 되기 때문이다.

하지만, 각 loop의 결과 값은 0이 아닐 것이다.

1. 왜 0이 아닐까?

이유부터 말하자면 실제 연산되는 순서는 한줄이 아닌 여러줄에 걸쳐서 연산이 이뤄지기 때문이다.

number++로 예를 들면, 한줄의 코드는 실제 실행될 때 다음과 같은 과정을 거친다.

  1. number의 메모리주소의 값을 레지스터에 저장한다.
  2. 레지스터의 값을 1 증가시킨다.
  3. number의 메모리주소에 레지스터값을 입력한다.

위 3개의 단계를 Thread1, Thread2가 수행한다고 하자 다음과 같은 일이 일어나면 증감을 한번씩 했다해도 number가 0이 아닐 수 있다.

Thread1								Thread2
1. number값을 가져온다. // 0
2. 레지스터 값을 1 증가시킨다. // 1
									1. number값을 가져온다. // 0
3. 메모리주소에 레지스터값을 입력한다. // 1
                            		2. 레지스터 값을 1 감소시킨다. // -1
                                    3. 메모리주소에 레지스터값을 입력한다. // -1

위 순서대로 진행된다면 1증가 1감소로 0이 될것이라 예상하지만 number는 -1이 된다.

2. InterLocked

위 상황을 해결하기 위해 실제 연산하는 부분에 대해서 다른 쓰레드가 공유 자원에 접근하지 못하도록 할 필요가 있다.

공유자원에 접근하는 코드 부분을 임계구역(Critical Section)이라한다. 이에 대해서는 나중에 글을 작성할 예정이다.

number의 증감연산을 한번의 연산으로 나타내기 위해 C#에는 InterLocked가 존재한다. 하지만, InterLocked는 정수값에 대한 연산의 동시성을 제어해주기 때문에 더 복잡한 상황에서는 다른 방법을 사용해야 한다.

문제상황을 해결하기 위해 Thread1, Thread2의 내용을 다음과 같이 바꿔준다. Interlocked.IncrementInterlocked.Decrement를 사용함으로 증감연산의 결과를 보장받을 수 있고 실행결과는 0이 나오게 된다.

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

    static void Thread2()
    {
        for (int i = 0; i < 100000; i++)
        	Interlocked.Decrement(ref number);
    }
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글