-
Interlocked게임서버/멀티쓰레드 프로그래밍 2022. 3. 10. 15:19
1. 경합조건(Race Condition)
using System; using System.Threading; using System.Threading.Tasks; 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); } } }
위의 코드를 분석해보면
Thread_1()에서는 number의 값을 10만을 늘렸고,
Thread_2()에서는 number의 값을 10만을 줄였다.
결국 둘을 합치면 0이 나와야한다.
실행결과 하지만 실행결과에서는 전혀 엉뚱한 값이 나온다. 그리고 매번 실행할 때 마다 이 값은 시시각각 변한다.
원자성
++ 혹은 -- 연산자는 우리가 쓰는 코드에서는 1줄로 보이지만 어셈블리 언어 단계까지가면 사실 여러줄의 코드로 이루어져있다.
number++를 좀 더 직관적으로 표현해보자면
int temp = number; temp += 1; number = temp;
위와같이 여러단계로 표현할 수 있다. 그리고 위의 코드는 더이상 쪼갤수가 없는 단위인데 이를 원자성이라고 한다.
static void Thread_1() { for (int i = 0; i < 100000; i++) { int temp = number; temp += 1; number = temp; } } static void Thread_2() { for (int i = 0; i < 100000; i++) { int temp = number; temp -= 1; number = temp; } }
원자성으로 표현하여 코드를 다시 써보면 위와같이 표현할 수 있는데.
Thread_1()과 Thread_2()는 거의 3줄의 코드가 동시에 진행되기 때문에 number의 값이 꼬이면서 이상한 값이 나오게 되는 것인데 이것을 경합조건(Race Condition)이라고 한다. 경합조건을 해결하기 위해서는 3줄의 코드가 1줄의 코드처럼 실행되면 되는데 이를위해서 원자단위로 진행하는 Interlocked 명령어가 있다. 사용방법은 아래와 같다.
static void Thread_1() { for (int i = 0; i < 100000; i++) { Interlocked.Increment(ref number); // number++ } } static void Thread_2() { for (int i = 0; i < 100000; i++) { Interlocked.Decrement(ref number); // number-- } }
이렇게 코드를 수정하고 실행해보면 정상적으로 0이 출력되는 것을 확인할 수 있다.
주의해야할 점은 Interlocked계열의 함수를 사용하면 성능적인 면에서 손해를 본다는 점이다.
참고로, Interlocked는 내부에 메모리 베리어가 간접적으로 포함되어있다.
2. 싱글쓰레드 마인드 버리기
static void Thread_1() { for (int i = 0; i < 100000; i++) { int prev = number; Interlocked.Increment(ref number); // number++ int next = number; } } static void Thread_2() { for (int i = 0; i < 100000; i++) { Interlocked.Decrement(ref number); // number-- } }
prev에 number값 넣어주고 number값을 1증가 시킨후 next에 다시 number값 저장하였다.
그러면 next에는 number++의 값이 들어갈 것 같다는 느낌이들지만 실제로는 그렇지 않을수도 있다.
왜냐하면 Thread_2()에서도 number의 값에 접근하고 있기 때문이다. 이처럼 쓰레드끼리 꼬이는 경우를 항상 생각하여야한다.