

- Race condition이란?
두 개 이상의 프로세스나 스레드가 공유된 데이터에 동시에 접근하려고 할 때, 그 순서에 따라 실행 결과가 달라질 수 있는 상황을 말한다.
즉, 공유된 자원에 대한 접근 순서가 결과에 영향을 미치는 상황.
주요 문제는 number++와 number-- 연산이 '원자적(atomic)이지 않다'는 것.
원자적 연산은 중간 단계 없이 한 단계로 완전히 수행되는 연산을 의미한다.
number++나 number-- 연산은 세 부분으로 나뉩니다:
number++;의 연산 과정
메모리로부터 값 읽기
: CPU는 메모리 주소에서 현재 값을 읽어 레지스터로 가져온다. 이 단계에서 메모리에서 현재 number의 값을 CPU로 로드한다.
연산 수행
: CPU 내부에서 레지스터의 값을 증가시키거나 감소시킨다. 이 경우 number의 값이 1 증가하거나 감소한다.
메모리에 값 쓰기
: 연산된 결과를 다시 메모리 주소에 저장한다. 변경된 number의 값이 메모리에 저장된다.

CPU명령에서 '원자적으로 연산'한다.
ㄴ 결과값: 0
(ref number)로 number변수의 주소값을 참조(ref)하여 해당 주소값에 있는 number의 값을 1씩 증가/감소시키기 때문에 '원자적'연산이 가능하다.
ㄴ 해당 값을 읽고 넘겨서 더하고 다시 넘겨주는 과정이 없기 때문!
namespace ServerCore
{
internal 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의 경우 이미 메모리 배리어가 일어나고 있기 때문에 가시성을 위해 변수에 volatile을 굳이 작성하지 않아도 된다.
Thread_1()과 Thread_2()가 경합하여 만약 Thread_1()이 먼저 시작했다면, Interlocked.Increment()가 다 실행된 후에야 Interlocked.Decrerment()가 실행될 수 있다.
즉, 순서를 보장받는다.
❌ 하지만, Race condition을 Interlocked 코드로 해결하는 것의 가장 치명적인 문제는 'int 정수'만 사용할 수 있다는 것이다..!
즉, 나만 사용할 것이다. 다른 사람들은 얼씬도 하지말라는 뜻.
ㄴ 이렇게 진행하면 number++;이 진행되는 동안 number--;가 실행되지 못하기 때문에 race condition의 문제가 해결된다. 현재처럼 Monitor.Enter()와 Exit() 사이에 실행할 코드가 간결하면 괜찮은데, 만약 코드가 길어져서 실수로 안에 return;을 작성해버리면 Exit()로 잠금을 풀어주지 못했기 때문에 코드가 마무리되지 못하고 그 안에서 갇혀버리는 현상이 발생한다.
이것이 데드락이다.
💡 데드락이 되지 않도록 하려면?
ㄴ 하지만 번거롭다... 
namespace ServerCore
{
internal class Program
{
static int number = 0;
static object _obj = new object();
static void Thread_1()
{
for (int i = 0; i < 1000000; i++)
{
lock (_obj)
{
number ++;
}
}
}
static void Thread_2()
{
for (int i = 0; i < 1000000; i++)
{
lock (_obj)
{
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);
}
}
}