Event는 멀티쓰레드 환경에서 쓰레드 간의 동기화나 통신을 위해 사용된다. 한 쓰레드는 다른 쓰레드에게 데이터가 처리 되었음을 알리는 Event나 정보가 준비되었다는 Event를 보낼 수 있다. 이러한 기능을 이용해 Lock에 적용할 수 있는데 Event를 공유자원이 준비되었을 때 발생시키는 것으로 준비되었음을 알릴 수 있다.
Event의 동작은 Set
, Reset
, Wait
이 존재하는데 Set
, Reset
는 Event의 상태값을 변경하는데 사용되고 Wait
은 Event가 발생할 때까지 기다리는데 사용된다.
C#에는 Event의 종류로는 자동 이벤트(AutoReset Event)와 수동 이벤트(ManualReset Event)가 있는데 두 Event의 차이는 Wait
이후 Reset
의 여부인데 자동 이벤트는 Reset
을 따로 안해주어도 되고 수동 이벤트는 Reset
을 따로 해주어야 한다.
이후에 나올 예제 코드는 SpinLock에서 다른 증감 예제로 Lock의 바뀐 부분만 다룬다.
AutoResetEvent를 이용해 Lock을 구현한 코드는 아래와 같다. Lock Class는 AutoResetEvent 객체를 기준으로 Acquire()
과 Release()
를 한다.
AutoResetEvent를 생성할 때 활성화 여부를 지정해주어야 하며 true
는 활성되가 되어 있어 Wait
을 호출한 쓰레드가 진행할 수 있음을 의미하고 false
는 Wait
을 호출한 쓰레드가 Event가 활성화 될때까지 기다려야 함을 나타낸다.
class Lock
{
AutoResetEvent _available = new AutoResetEvent(true);
public void Acquire()
{
_available.WaitOne();
}
public void Release()
{
_available.Set();
}
}
Event의 Wait
은 WaitOne()
으로 실행할 수 있으며 AutoReset Event의 특징은 WaitOne()
에서 자동으로 Event를 비활성화 시켜준다는 것이다. 즉, 위 상황은 한 쓰레드가 WaitOne()
를 통해 Lock을 얻었다면 Event는 비활성화 되어 다른 쓰레드가 동시에 Lock을 얻을 수 없음을 보장할 수 있다.
위의 코드로 main을 실행하면
_num
은 0으로 출력된다.
ManualResetEvent는 AutoResetEvent와 거의 비슷하다. 단지, WaitOne()
이후에 Reset()
을 따로 해주어야 한다. ManualResetEvent로 Lock을 구현한 코드는 아래와 같다.
class Lock
{
ManualResetEvent _available = new ManualResetEvent(true);
public void Acquire()
{
_available.WaitOne();
_available.Reset();
}
public void Release()
{
_available.Set();
}
}
하지만, 위의 코드로 구현한 Lock Class를 이용해 main함수를 실행시키면 _num
은 0이 아닐때가 있다. 그 이유는 Lock을 얻는 WaitOne()
과 Reset()
을 한번에 하지 못했기 때문인데, 두 부분의 실행시간 차이가 동시에 실행되는 쓰레드 사이에서 2개 이상의 쓰레드가 Lock을 얻을 수 있기 때문이다.
이러한 문제로 인해 발생되는 상황은 SpinLock에서 다룬적이 있다.