원자성이란?
여러 스레드가 존재할 때 특정 시점에서 어떠한 메소드를 두개 이상의 스레드가 동시에 호출하지 못한다.
멀티 스레드를 사용할 때 중요한 것 중에 하나가 바로 원장성이다. Interlocked는 이러한 원자성을 보장해주는 역할을 한다.
예를 들어 손님 1명과 직원 3명이 존재한다.
손님이 주문을 하여 콜라를 받아야할 상황일 때 직원 3명이 동시에 콜라를 전달해 준다면 이는 잘못된 방식일 것이다.
이러한 상황을 경합 조건
이라 한다.
만약 여기에 원자성을 보장
한다 가정하면 3명의 직원 중 제일 전달이 빠른 1명이 콜라를 전달하게 되고
나머지 직원을 콜라 전달에 대해 알게 되기 때문에 콜라 전달을 멈춘다.
💻 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static int number = 0;
static void Thread_1()
{
// 1000000번 number++
for(int i=0; i<1000000; i++)
{
Interlocked.Increment(ref number);
}
}
static void Thread_2()
{
// 1000000번 number--
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);
}
number++ 과 Interlocked의 차이
- number++ 같은 경우 변수를 가져와서 사용을 하기 때문에 다른 스레드보다 더 늦을 수 밖에 없다.
- Interlocked는 number++ 같은 상황을 해결하기 위해 매개변수 값을 ref값(주소값)으로 받아 해당 주소로 가서 + / - 연산을 해준다. 이 덕분에 참조된 값은 몰라도 주소를 알기 때문에 변수를 가져오지 않고 주소로 바로 가서 연산을 수행할 수 있다.
Interlocked 값 확인
원자성이 보장된 Interlocked를 진행한 값을 확인하고 싶다면 number 값을 Debug해선 안된다.
왜냐하면 다른 스레드에서 이미 number라는 값을 사용하고 있기 때문이다.
이 때문에 연산된 값을 알고 싶다면 Interlocked의 리턴값을 받아야 한다.
O : int afterValue = Interlocked.Increment(ref number);
X : int afterValue = number;