C#

Event를 통한 Lock 구현

monstro 2024. 12. 4. 12:25
728x90
반응형

이번에는 Lock을 구현하는 3가지 방법 중에서 Event를 통해 Lock을 구현하는 방법을 알아보겠습니다.

Event를 통해 Lock을 구현하는 방식은 2가지가 존재하는데,

자동으로 동작하는 AutoResetEvent수동으로 조작해야 하는 ManualResetEvent가 존재합니다.

 

1) AutoResetEvent

제일 먼저 AutoResetEvent를 통해 Lock을 구현해보도록 하겠습니다.

코드는 다음과 같습니다.

class Lock
{ 
    // AutoResetEvent의 true 또는 false 값에 따라 해당 이벤트가 사용가능한 상태를 의미함
    AutoResetEvent _available = new AutoResetEvent(true);

    public void Acquire()
    {
        // 이벤트를 사용시도, 사용이 완료된 후 AutoResetEvent는 자동으로 이벤트를 비활성화함
        _available.WaitOne();
    }

    public void Release() 
    {
        // 사용이 끝난 이벤트를 다시 사용가능한 상태로 만듬
        _available.Set();
    }
}

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

    static void Thread_1()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.Acquire();
            _num++;
            _lock.Release();
        }
    }

    static void Thread_2()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.Acquire();
            _num--;
            _lock.Release();
        }
    }

    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(_num);
    }
}

 

위와 같이 구성된 코드를 확인할 수 있습니다.

AutoResetEvent의 WaitOne 함수이벤트의 사용이 끝나면 해제하는 것도 자동으로 수행합니다

내부적으로 Reset() 함수가 포함되어 AutoResetEvent를 false로 만듭니다.

 

그리고 Set() 함수AutoResetEvent를 다시 true 상태로 만들게 됩니다.

이제 실행결과를 살펴보겠습니다.

 

 

Event를 통해 스핀락의 구현이 문제없이 이뤄진 것을 확인할 수 있습니다.

이번에는 ManualResetEvent를 사용해보겠습니다.

 

2) ManualResetEvent

class Lock
{ 
    // ManualResetEvent
    ManualResetEvent _available = new ManualResetEvent(false);

    public void Acquire()
    {
        // ManualResetEvent의 경우 이벤트를 비활성화하는 것도 직접 수행해야 함
        _available.WaitOne();
        _available.Reset();
    }

    public void Release() 
    {
        _available.Set();
    }
}

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

    static void Thread_1()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.Acquire();
            _num++;
            _lock.Release();
        }
    }

    static void Thread_2()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.Acquire();
            _num--;
            _lock.Release();
        }
    }

    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(_num);
    }
}

 

위와 같은 코드를 사용합니다.

이제 실행결과를 살펴보겠습니다.

 

구현한 스핀락이 제대로 돌아가지 않는 것을 확인할 수 있습니다.

그 이유는 

public void Acquire()
{
    _available.WaitOne();
    _available.Reset();
}

 

에서 사용과 사용 후 비활성화가 한번에 이뤄지지 않고 쪼개져 있기에 발생합니다.

따라서 이를 한번에 수동으로 처리해줘야 합니다.

 

그렇다면 왜 MaualResetEvent를 사용하는 걸까요? 그 이유는 다음과 같습니다.

수동으로 조정해야 하는 ManualResetEvent의 경우 활성화되면 모든 대기중인 스레드를 해제합니다.

반대로 자동으로 조정해야 하는 AutoResetEvent의 경우 활성화되면 대기중인 단일 스레드만을 해제합니다.

따라서 MaualResetEvent의 경우 스레드가 많은 경우에 사용하는 것이 적합합니다.

 

3) Mutex

마지막으로 Mutex를 통한 락을 구현해보도록 하겠습니다.

Mutex를 사용하는 방식은 스핀락 계열이 아니므로 코드가 다음과 같이 구성됩니다.

 

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

    static void Thread_1()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.WaitOne();
            _num++;
            _lock.ReleaseMutex();
        }
    }

    static void Thread_2()
    {
        for (int i = 0; i < 10000; i++)
        {
            _lock.WaitOne();
            _num--;
            _lock.ReleaseMutex();
        }
    }

    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(_num);
    }
}

 

실행결과를 살펴보면 다음과 같습니다.

 

Mutex의 경우, 실행결과를 스핀락과 비교해 보면 느린것을 확인할 수 있습니다.

그 이유는 Mutex의 경우 Event에 비해 내부적으로 많은 정보를 들고 있기 때문입니다.

Mutex가 몇번 Lock되었는지어떤 스레드에서 Mutex를 Lock하였는지와 같은 정보를 들고 있기에

Mutex의 동작은 Event에 비해 느립니다. 하지만 이 정보를 사용하여 필요한 부분에서 활용할 수 있습니다.

728x90
반응형

'C#' 카테고리의 다른 글

ReaderWriterLock 구현  (0) 2024.12.09
Lock 구현 훑어내리기 + ReaderWriterLock 기초  (0) 2024.12.04
Context Switching  (0) 2024.12.04
SpinLock  (0) 2024.12.04
DeadLock과 Lock구현  (0) 2024.12.02