'Monitor 클래스'로 동기화

00·2025년 1월 9일

C#

목록 보기
141/149

Monitor 클래스로 동기화

Monitor 클래스

Monitor 클래스는 lock 키워드와 유사한 기능을 제공하지만, 좀 더 세밀하게 스레드 동기화를 제어할 수 있도록 하는 도구이다.

Monitor 클래스는 lock 키워드와 달리, 잠금을 획득하고 해제하는 Enter() 메서드와 Exit() 메서드를 명시적으로 호출해야 한다.

또한, Monitor 클래스는 Wait() 메서드와 Pulse() 메서드를 제공하여 스레드 간의 신호 전달을 통해 더욱 정교한 동기화를 구현할 수 있도록 한다.

Monitor 클래스의 주요 기능

  • Enter() 메서드: 지정된 객체에 잠금을 건다.
  • Exit() 메서드: 지정된 객체의 잠금을 해제한다.
  • TryEnter() 메서드: 지정된 객체에 잠금을 시도하고, 잠금을 획득하면 true를, 잠금을 획득하지 못하면 false를 반환한다.
  • Wait() 메서드: 잠금을 해제하고, 다른 스레드가 Pulse() 메서드 또는 PulseAll() 메서드를 호출할 때까지 현재 스레드를 대기 상태로 만든다.
  • Pulse() 메서드: 대기 중인 스레드 하나를 깨운다.
  • PulseAll() 메서드: 대기 중인 모든 스레드를 깨운다.

Monitor 클래스 사용 예시

using System;
using System.Threading;

class Counter
{
    private int count = 0;
    private readonly object lockObject = new object();

    public void Increase()
    {
        Monitor.Enter(lockObject);  // lockObject 객체에 잠금을 겁니다.
        try
        {
            count++;
        }
        finally
        {
            Monitor.Exit(lockObject);  // finally 블록에서 잠금을 해제합니다.
        }
    }
}

lock 키워드 vs Monitor 클래스

  • 세밀한 제어: lock 키워드는 Monitor 클래스를 사용하는 것보다 간결하고 사용하기 쉽다.Monitor 클래스는 TryEnter(), Wait(), Pulse(), PulseAll() 메서드를 사용하여 잠금을 더 세밀하게 제어할 수 있다.
  • 타임아웃: TryEnter() 메서드는 타임아웃을 설정하여 잠금을 시도할 수 있다. Monitor.TryEnter() 메서드를 사용하면 잠금을 획득하지 못했을 때 대기하지 않고 다른 작업을 수행할 수 있다.
  • 대기 및 알림: Wait(), Pulse(), PulseAll() 메서드를 사용하여 스레드 간의 대기 및 알림 메커니즘을 구현할 수 있다.

Monitor 클래스 사용 시 주의 사항

  • Monitor.Enter() 메서드로 잠금을 건 후에는 반드시 Monitor.Exit() 메서드로 잠금을 해제해야 한다. 잠금을 해제하지 않으면 다른 스레드가 해당 객체에 접근할 수 없어 데드락이 발생할 수 있다.
  • try-finally 블록을 사용하여 예외 발생 시에도 잠금이 해제되도록 하는 것이 좋다.
  • Wait() 메서드와 Pulse() 메서드는 반드시 lock 문 내부에서 호출해야 한다.

Monitor 클래스는 lock 키워드보다 더욱 세밀한 제어가 가능하지만, 사용하기 복잡하고 오류 발생 가능성이 높으므로 주의해서 사용해야 한다.

Monitor 클래스 사용 예시

using System;
using System.Threading;

namespace UsingMonitor
{
    class Counter
    {
        const int LOOP_COUNT = 1000; // 반복 횟수를 나타내는 상수이다.

        readonly object thisLock; // 잠금 객체이다.

        private int count; // 카운터 값을 저장하는 필드이다.
        public int Count // count 필드에 접근하기 위한 프로퍼티이다.
        {
            get { return count; }
        }

        public Counter() // 생성자이다.
        {
            thisLock = new object(); // 잠금 객체를 초기화한다.
            count = 0; // 카운터 값을 0으로 초기화한다.
        }

        public void Increase() // 카운터 값을 증가시키는 메서드이다.
        {
            int loopCount = LOOP_COUNT; // 반복 횟수를 loopCount 변수에 저장한다.
            while (loopCount-- > 0) // loopCount가 0보다 클 때까지 반복한다.
            {
                Monitor.Enter(thisLock); // 잠금 객체를 사용하여 임계 영역을 보호한다.
                try
                {
                    count++; // 카운터 값을 1 증가시킨다.
                }
                finally
                {
                    Monitor.Exit(thisLock); // 잠금을 해제한다.
                }
                Thread.Sleep(1); // 1밀리초 동안 스레드를 일시 중지한다.
            }
        }

        public void Decrease() // 카운터 값을 감소시키는 메서드이다.
        {
            int loopCount = LOOP_COUNT; // 반복 횟수를 loopCount 변수에 저장한다.
            while (loopCount-- > 0) // loopCount가 0보다 클 때까지 반복한다.
            {
                Monitor.Enter(thisLock); // 잠금 객체를 사용하여 임계 영역을 보호한다.
                try
                {
                    count--; // 카운터 값을 1 감소시킨다.
                }
                finally
                {
                    Monitor.Exit(thisLock); // 잠금을 해제한다.
                }
                Thread.Sleep(1); // 1밀리초 동안 스레드를 일시 중지한다.
            }
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Counter counter = new Counter(); // Counter 객체를 생성한다.

            Thread incThread = new Thread(new ThreadStart(counter.Increase)); // Increment 메서드를 실행할 스레드를 생성한다.
            Thread decThread = new Thread(new ThreadStart(counter.Decrease)); // Decrement 메서드를 실행할 스레드를 생성한다.

            incThread.Start(); // incThread 스레드를 시작한다.
            decThread.Start(); // decThread 스레드를 시작한다.

            incThread.Join(); // incThread 스레드가 종료될 때까지 기다린다.
            decThread.Join(); // decThread 스레드가 종료될 때까지 기다린다.

            Console.WriteLine(counter.Count); // 카운터 값을 출력한다.
        }
    }
}

코드 설명

이 C# 코드는 Monitor 클래스를 사용하여 스레드 동기화를 구현하는 예제이다. Counter 클래스는 count 필드를 갖고 있으며, Increase() 메서드는 count를 증가시키고, Decrease() 메서드는 count를 감소시킨다.

MainApp 클래스의 Main 메서드에서는 Counter 객체를 생성하고, Increase() 메서드와 Decrease() 메서드를 각각 실행하는 두 개의 스레드를 생성한다. 두 스레드는 동시에 실행되면서 count 필드에 접근하고 변경하기 때문에, 데이터 경쟁이 발생할 수 있다.

이를 방지하기 위해 Monitor 클래스를 사용하여 count 필드에 대한 접근을 동기화한다. Monitor.Enter(thisLock) 문은 thisLock 객체에 잠금을 걸고, 잠금을 획득한 스레드만 count 필드에 접근할 수 있도록 한다. 다른 스레드는 잠금이 해제될 때까지 대기한다. try-finally 블록을 사용하여 예외 발생 시에도 Monitor.Exit(thisLock) 문이 실행되어 잠금이 해제되도록 한다.

Thread.Sleep(1) 문은 스레드를 1밀리초 동안 일시 중지하여, 다른 스레드가 실행될 기회를 줍니다. 이는 데이터 경쟁을 더욱 명확하게 보여주기 위한 것이다.

incThread.Join()decThread.Join()은 각 스레드가 종료될 때까지 기다리는 메서드이다. 모든 스레드가 종료된 후, Console.WriteLine(counter.Count); 문은 최종 카운터 값을 출력한다.

출력 결과

0

코드 해석

이 코드는 스레드 동기화의 중요성을 보여주는 예제이다. Monitor 클래스를 사용하지 않으면, 두 스레드가 동시에 count 필드에 접근하고 변경하기 때문에 데이터 경쟁이 발생하고 예상치 못한 결과가 출력될 수 있다. 하지만 Monitor 클래스를 사용하여 count 필드에 대한 접근을 동기화하면 데이터 경쟁을 방지하고 정확한 결과를 얻을 수 있다.

thisLock = new object();

thisLock = new object();thisLock이라는 변수에 새로운 object 객체를 생성하여 할당하는 코드이다.

thisLockCounter 클래스 내에서 readonly로 선언된 필드로, 스레드 동기화를 위한 잠금 객체로 사용된다.

object 클래스는 .NET의 '모든 클래스의 기본 클래스'이며, 잠금 객체로 사용하기에 적합하다.

new 키워드는 새로운 객체를 생성하는 데 사용되며, object()object 클래스의 인스턴스를 생성하는 생성자이다.

따라서 thisLock = new object();thisLock 필드에 새로운 object 객체를 생성하여 할당하고, 이 객체는 Increment() 메서드와 Decrement() 메서드에서 lock 문의 잠금 객체로 사용된다.

0개의 댓글