Monitor와 lock은 모두 객체에 대한 접근을 동기화하는 메커니즘을 제공합니다. 사실 lock 키워드는 Monitor.Enter를 try-finally 블록과 함께 사용하는 형태의 축약형(문법적 설탕)입니다.
lock은 문법이 간결하여 대부분의 기본적인 스레드 동기화 상황에서 가장 권장되는 방식입니다. 하지만 TryEnter(), Wait(), Pulse(), PulseAll()과 같은 메서드를 활용하여 타임아웃 처리나 스레드 간 신호 전달 같은 고급 멀티스레딩 솔루션을 구현해야 한다면, 보다 세밀한 제어가 가능한 Monitor 클래스를 직접 사용하는 것이 좋습니다.
lock 사용 예제
다음 예제는 lock 키워드를 사용해 100번의 증분 작업을 안전하게 수행합니다.
class Program{
static object _lock = new object();
static int Total;
public static void Main(){
AddOneHundredLock();
Console.ReadLine();
}
public static void AddOneHundredLock(){
for (int i = 1; i <= 100; i++){
lock (_lock){
Total++;
}
}
}Monitor 사용 예제
아래 코드는 위의 lock 예제를 Monitor 클래스로 표현한 것입니다. lock 키워드가 내부적으로 어떻게 동작하는지 이해하는 데 도움이 됩니다. 임계 영역(critical section)을 반드시 finally 블록에서 해제하여 예외 발생 시에도 잠금이 정상적으로 반환되도록 해야 합니다.
class Program{
static object _lock = new object();
static int Total;
public static void Main(){
AddOneHundredMonitor();
Console.ReadLine();
}
public static void AddOneHundredMonitor(){
for (int i = 1; i <= 100; i++){
Monitor.Enter(_lock);
try{
Total++;
}
finally{
Monitor.Exit(_lock);
}
}
}
}정리
- lock: 기본적인 동기화에 적합하며, 컴파일러가 try-finally를 자동으로 생성해 안전하고 간결합니다.
- Monitor: Enter/Exit 외에 TryEnter로 데드락 방지, Wait/Pulse/PulseAll로 스레드 간 통신 등 고급 제어가 필요할 때 사용합니다.