공부/게임서버

AutoResetEvent

samosa 2024. 11. 14. 17:49
namespace ServerCore;

internal class Lock
{
    AutoResetEvent _available = new AutoResetEvent(true); // true: available, false: not available

    public void Enter()
    {
        _available.WaitOne(); // if available, continue, if not, wait
    }

    public void Leave()
    {
        _available.Set(); // set available to true
    }
}

internal class Program
{
    static int _num = 0;
    static Lock _lock = new Lock();

    public static void Thread_1()
    {
        for (int i = 0; i < 1000000; i++)
        {
            _lock.Enter();
            _num++;
            _lock.Leave();
        }
    }

    public static void Thread_2()
    {
        for (int i = 0; i < 1000000; i++)
        {
            _lock.Enter();
            _num--;
            _lock.Leave();
        }
    }

    private static void Main(string[] args)
    {
        var t1 = new Task(Thread_1);
        var t2 = new Task(Thread_2);
        t1.Start();
        t2.Start();

        Task.WaitAll(t1, t2);

        Console.WriteLine(_num);
    }
}

이 코드 예제는 AutoResetEvent를 이용해 스레드 간의 동기화를 구현한 간단한 잠금 메커니즘이다. Lock 클래스는 스레드가 임계 구역에 진입하기 위해 사용할 수 있는 EnterLeave 메서드를 제공한다. 이 예제를 기반으로 각 부분을 설명한다.

코드 설명

Lock 클래스

  • AutoResetEvent _available: 잠금 상태를 관리하는 AutoResetEvent 객체이다. 초기값 true는 리소스가 사용 가능함을 의미한다.
  • Enter() 메서드:
    • WaitOne()을 호출해 스레드가 _available의 신호 상태를 대기한다.
    • _available이 신호 상태(true)일 경우, WaitOne()은 즉시 반환되어 스레드가 임계 구역에 진입할 수 있다.
    • _available의 상태가 false라면, WaitOne()_available이 신호 상태가 될 때까지 대기한다.
  • Leave() 메서드:
    • Set()을 호출하여 _available의 상태를 true로 설정해 다음 대기 중인 스레드가 임계 구역에 진입할 수 있게 한다.

이 메커니즘은 한 번에 하나의 스레드만 임계 구역에 진입할 수 있도록 보장한다. 즉, AutoResetEvent가 신호 상태를 자동으로 false로 재설정하기 때문에 다른 스레드가 임계 구역에 진입할 때까지 대기하게 된다.

Program 클래스

  • _num 변수: 두 스레드가 공유하는 변수로, 동기화되지 않으면 경합 상태에서 불일치한 결과가 나올 수 있다.
  • Thread_1():
    • _lock.Enter()로 잠금을 시도하고, 성공하면 _num을 증가시킨 후 _lock.Leave()로 잠금을 해제한다.
  • Thread_2():
    • _lock.Enter()로 잠금을 시도하고, 성공하면 _num을 감소시킨 후 _lock.Leave()로 잠금을 해제한다.
  • Main() 메서드:
    • Thread_1Thread_2를 각각 새로운 Task로 시작하고, 두 스레드의 완료를 Task.WaitAll()로 기다린다.
    • 프로그램 실행 후 _num의 값을 출력한다.

동작 흐름

  1. Thread_1Thread_2_lock.Enter()를 호출해 _available의 상태를 확인한다.
  2. 만약 _availabletrue라면 WaitOne()이 즉시 반환되고, 스레드는 임계 구역에 진입한다. _available은 자동으로 false로 바뀐다.
  3. 스레드는 임계 구역에서 _num을 변경한 후, _lock.Leave()를 호출해 _available.Set()으로 상태를 true로 바꾸어 다음 스레드가 임계 구역에 진입할 수 있게 한다.
  4. 두 스레드가 순차적으로 잠금을 획득하여 _num을 변경하므로, AutoResetEvent를 통해 다중 스레드 환경에서도 안전하게 동기화된다.

AutoResetEvent의 역할

AutoResetEvent는 한 번에 하나의 스레드만 임계 구역에 들어가도록 하는 역할을 한다. 스레드가 WaitOne()을 호출해 _available이 신호 상태(true)라면 자동으로 _available이 비신호 상태(false)로 전환되어 다른 스레드는 대기 상태가 된다. Leave() 메서드에서 Set()을 호출하면 _available이 다시 true로 전환되어 다음 대기 중인 스레드가 실행될 수 있다.

장점 및 단점

  • 장점: 코드가 간단하며, AutoResetEvent를 통해 간단한 동기화 메커니즘을 쉽게 구현할 수 있다.
  • 단점: 스핀락에 비해 컨텍스트 스위칭 비용이 발생할 수 있어, 성능 면에서 제한이 있을 수 있다. 많은 스레드가 동시에 대기할 경우, 성능 저하가 발생할 수 있다.

이 코드는 스레드 동기화 문제를 다루는 기본적인 예제로, 다중 스레드 환경에서 공유 자원의 안전한 접근을 보장한다.