Interlocked 함수로 Race Condition을 해결할 수 있지만 한 문장 한 문장 일일히 Interlocked을 수행할 수는 없는 노릇이다.
블록단위로 원자성을 보장할 수 있게 해주는 방법이 필요하다.
첫 번째 방법은 Monitor를 이용하는 방법이다.
class Program
{
static int number = 0;
static object _obj = new object();
static void Thread_1()
{
for(int i=0; i<100000; i++)
{
Monitor.Enter(_obj);
number++;
Monitor.Exit(_obj);
}
}
static void Thread_2()
{
for(int i=0; i<100000; i++)
{
Monitor.Enter(_obj);
number--;
Monitor.Exit(_obj);
}
}
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);
}
}
Monitor.Enter()와 Monitor.Exit()을 이용하여 작업하는 와중에 잠금을 걸 수가 있다.
이는 화장실의 문을 잠근 것과 비슷하다.
화장실의 문을 잠고 -> 볼 일을 본 다음 -> 잠금을 해제하고 나오는 구조라고 생각하면 쉽다.
여기서 자물쇠의 열쇠에 해당하는 게 _obj (오브젝트 객체)라고 보면 된다.
허나 이런 식의 방법 역시 치명적인 결함이 존재한다.
static void Thread_1()
{
for(int i=0; i<100000; i++)
{
Monitor.Enter(_obj);
number++;
if(number>20000)
{
return;
}
Monitor.Exit(_obj);
}
}
만약에 Thread_1 함수에 number가 20000보다 크면 return으로 종료하는 코드를 넣어주었다고 해보자.
그럼 다음과 같이 코드가 종료되지 않는 경우가 발생한다.
이는 return 문이 먼저 실행되어 Monitor.Exit 함수가 실행되지 않았기 때문이다.
화장실의 문이 영원히 잠궈서 다른 사람이 작업을 하지 못하는 경우이다.
이를 데드락 현상 (교착상태) 이라고 한다.
이 문제를 해결하기 위한 lock 함수로 간단히 해결할 수 있다.
lock (_obj)
{
number++;
if (number > 20000)
{
Console.WriteLine("number > 20000");
return;
}
}
Thread_1 함수에서 Mointor 대신에 lock으로 편리하게 구현할 수 있다.
이러면 Mointor.Exit을 수행하지 못하여 데드락에 빠지는 경우를 예방할 수 있다.
아니면
try
{
Monitor.Enter(_obj);
number++;
if (number > 20000)
{
Console.WriteLine("number > 20000");
return;
}
}
finally
{
Monitor.Exit(_obj);
}
try- finally 구문을 사용하여 try에서 코드가 종료되어도 finally에서 반드시 실행하도록 구현하는 방법도 있다.
결과는 모두 정상적으로 0이 출력되는 것을 볼 수 있다.