Server/Study
[Server] Interlocked
GiveZero
2023. 4. 26. 11:49
using System;
namespace Server
{
class Program
{
private static int number = 0;
static void Thread1()
{
for (int i = 0; i < 10000; i++)
{
number++;
}
}
static void Thread2()
{
for (int i = 0; i < 10000; i++)
{
number--;
}
}
static void Main(string[] args)
{
Task t1 = new Task(Thread1);
Task t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
}
위와 같이 실행하면 0이 나오는가?
나오지 않음
이유 - Race Condition (경합조건)
static void Thread1()
{
for (int i = 0; i < 10000; i++)
{
number++;
}
}
위의 상황은 아래의 상황으로 이루어진다. (단 임시 변수가 아닌 레지스터로 이루어짐)
number++이라거나 number--와 같은 단순 덧셈 연산도
실제 바이너리 코드를 까보면 3단계에 걸쳐서
- 1) 데이터를 eax 레지스터에 갖고 오고
- 2) eax 값을 1 증가시키고
- 3) eax 값을 다시 데이터에 저장
형태로 이루어지기 때문에 쓰레드 사이에 경합이 일어나면 문제 발생
(2단계를 호출하고 3은 아직 안 한 상태에서, 다른 쓰레드가 끼어들어 1을 실행한다거나..)
static void Thread1()
{
for (int i = 0; i < 10000; i++)
{
int temp = number;
temp +=1;
number = temp;
}
}
한번에 일어나야할 일이 여러 동작으로 나누어 이루어 졌기 때문
-> critical section
임계 영역
원자성
number++를 atomic하게 만드는 Interlocked을 활용
static void Thread1()
{
for (int i = 0; i < 10000; i++)
{
// 성능의 문제가 발생 하지만 현재 상태의 해결책
Interlocked.Increment(ref number);
}
}
static void Thread2()
{
for (int i = 0; i < 10000; i++)
{
Interlocked.Decrement(ref number);
}
}
ref number 을 사용하는 이유
int a = number 와 같이 꺼내와 쓰면 다른 곳에서도 사용중일 수 있기 때문
그래서 Interlocked.Increment() 의 반환값은 int이고, 이것으로 검증가능