키를 아무도 안 가지고 있으면 알려달라고 부탁하고 알려주기 전까지 키를 얻을 수 있는지 검사를 하지 않아도 된다는 것인데 이 알려주는 커널에 부탁한다. 키를 얻을 수 있을때만 와도 된다는 장점이 있지만 느리다는 장점이 있다.
class Lock
{
AutoResetEvent _available = new AutoResetEvent(true);
public void Acquire()
{
_available.WaitOne(); // 입장을 시도한다.
//_available.Reset(); // 문을 닫아주는건데 이미 WaitOne에서 닫아주고 있어서 안해도 된다
}
public void Release()
{
_available.Set(); // 이벤트의 상태를 시그널로 바꾼다.(문을 열어준다.
}
}
// 나머지는 스핀락코드와 같다.
new AutoResetEvent(true)
초깃값을 true로 주면 AutoResetEvent안에 있는 bool값이 true로 설정된다. 이 bool값이 true는 현재 키를 얻을 수 있다는 의미이다.
available.WaitOne()
키를 얻는 시도를 하는 것으로 AutoResetEvent의 WaitOne은 키를 얻으면 자동으로 bool값을 false로 바꿔 다른 스레드가 들어오지 못 하도록 한다. 여기에서는 초깃값이 true이기때문에 맨 처음에 키를 얻고자하는 스레드는 키 얻기를 성공한다.
_available.Reset();
bool값을 false로 바꿔주는 것이지만 AutoResetEvent.WaitOne()에서 자동으로 해주기때문에 필요없다. 이것은 ManualResetEvent에 필요하다.
ManualResetEvent _available = new ManualResetEvent(true);
public void Acquire()
{
//ManualResetEvent은 자동으로 문을 닫아주지 않는다
//이것을 실행하면 0이 안나온다. 입장을 하고 문을 닫는 행동으로 따로 하기 때문
_available.WaitOne(); // 입장을 시도한다.
_available.Reset(); // 문을 닫아준다.
}
public void Release()
{
_available.Set(); // 이벤트의 상태를 시그널로 바꾼다.(문을 열어준다.
}
ManualResetEvent.WaitOne()은 자동으로 bool값을 false로 바꿔주지 않아. ManualResetEvent.Reset()를 해줘여한다. 하지만 한 스레드는 ++을 10000번 다른 스레드는 --를 10000번 했을때 0이 안된다. 그 이유는 키를 얻는 작업과 false를 해주는 작업을 동시에 실행하지 않아서 그렇다.
예를 들어 엄청 오래 걸리는 작업(로딩이라든지...) 그런 작업이 끝날때까지 모든 스레드들 묶어 뒀다가 엄청 오래 걸리는 작업이 끝나면 모든 스레드들이 다시 재가동하게 사용할 수도 있다.
ManualResetEvent _available = new ManualResetEvent(false);
public void Acquire()
{
_available.WaitOne(); // 입장을 시도한다.
}
public void Release()
{
_available.Set();
}
누군가 Release()를 호출할때까지 스레드들은 기다린다.
Event는 커널까지 가서 요청을 부탁하는 것을 알아야한다. SpinLock은 무한루프를 돌면서 유저 모드에서 실행하지만 Event는 운영체제한테 요청하는 것이라 큰 부담이 된다. 빠른 작업이 필요할때는 SpinLock을 사용하고 긴 대기 시간이 필요한 것은 Event를 사용해서 대기 시간이 끝나면 알려줘 스레드를 재가동하도록 하자.
추가로 Mutex라는 것이 있다.
static int _num = 0;
static Mutex _lock = new Mutex();
static void Thread_1()
{
for (int i = 0; i < 100000; i++)
{
_lock.WaitOne(); // 입장을 시도한다.
_num++;
_lock.ReleaseMutex(); // 문을 열어준다.
}
}
static void Thread_2()
{
for (int i = 0; i < 100000; i++)
{
_lock.WaitOne();
_num--;
_lock.ReleaseMutex();
}
}
이 코드도 실행하면 오래걸리는 것을 알 수 있다. Mutex도 마찬가지로 커널까지 간다. AutoResetEvent와 다른 점은 더 많은 정보를 가지고 있다는 것이다. AutoResetEvent가 bool값을 하나만 가지고 있다면 Mutex는 몇 번 잠궜는지 카운팅을 하기도 하고 스레드ID를 가지고 있어 누가 릴리즈를 실행할때 엉뚱한 스레드가 시도하고 있다는 사실도 알 수 있다. 그렇다 보니 추가 비용이 많이 든다.