[Thread] ManualResetEvent / AutoResetEvent
스핀 락과 같이 무한정으로 기다리는 스레드 대기 방식은 CPU 점유율이 올라가는 부담이 크다. 이럴 경우 스레드 대기가 끝나는 순간을 제 3자인 커널이 알려준다면 이러한 부담은 줄일 수 있을 것이다. ( 하지만 이 경우 커널 영역으로 요청을 보내기에 성능 소모가 크다)
이러한 방법은 C#에서 대표적으로 두 가지 클래스가 존재한다
🚀 ManualResetEvent
class Lock
{
// 생성자의 true : 락 진입 가능 여부
private ManualResetEvent mre = new ManualResetEvent(true);
public void Enter()
{
mre.WaitOne(); // 락 진입 시도 및 대기
mre.Reset();
}
public void Exit()
{
mre.Set(); // 락 해제
}
}
class Program
{
private static readonly Lock _lock = new Lock();
private const int Count = 100000;
private static int number = 0;
private static void ThreadBody1()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number++;
_lock.Exit();
}
}
private static void ThreadBody2()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number--;
_lock.Exit();
}
}
static void Main(string[] args)
{
Task t1 = new Task(ThreadBody1);
Task t2 = new Task(ThreadBody2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
-
ManualResetEvent의 WaitOne() 함수는 현재 lock을 가지고 있는 스레드가 없다면 임계 영역에 진입을 하게 된다.
-
Reset() 함수는 락을 설정하고 다른 스레드가 임계 구역에 못 들어오게 한다
-
Set() 함수는 락을 해제한다
-
위 코드의 경우 0이 출력된다고 예상하는 것이 일반적이겠지만,
class Lock
{
// 생성자의 true : 락 진입 가능 여부
private ManualResetEvent mre = new ManualResetEvent(true);
public void Enter()
{
mre.WaitOne(); // 락 진입 시도 및 대기
mre.Reset();
}
public void Exit()
{
mre.Set(); // 락 해제
}
}
class Program
{
private static readonly Lock _lock = new Lock();
private const int Count = 100000;
private static int number = 0;
private static void ThreadBody1()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number++;
_lock.Exit();
}
}
private static void ThreadBody2()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number--;
_lock.Exit();
}
}
static void Main(string[] args)
{
Task t1 = new Task(ThreadBody1);
Task t2 = new Task(ThreadBody2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
ManualResetEvent의 WaitOne() 함수는 현재 lock을 가지고 있는 스레드가 없다면 임계 영역에 진입을 하게 된다.
Reset() 함수는 락을 설정하고 다른 스레드가 임계 구역에 못 들어오게 한다
Set() 함수는 락을 해제한다
위 코드의 경우 0이 출력된다고 예상하는 것이 일반적이겠지만,
- ManualResetEvent는 임계 구역에 진입할지 확인하는(데이터를 읽는) WaitOne()과 락을 설정하는(데이터를 쓰는) Reset()이 원자성을 보장하지 못하기에 다른 값이 출력되는 것을 볼 수 있다
🚀 AutoResetEvent
class Lock
{
// 생성자의 true : 락 진입 가능 여부
private AutoResetEvent are = new AutoResetEvent(true);
public void Enter()
{
are.WaitOne(); // 락 진입 시도 및 대기
}
public void Exit()
{
are.Set(); // 락 해제
}
}
class Program
{
private static readonly Lock _lock = new Lock();
private const int Count = 100000;
private static int number = 0;
private static void ThreadBody1()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number++;
_lock.Exit();
}
}
private static void ThreadBody2()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number--;
_lock.Exit();
}
}
static void Main(string[] args)
{
Task t1 = new Task(ThreadBody1);
Task t2 = new Task(ThreadBody2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
- AutoResetEvent는 ManualResetEvent와 다르게 WaitOne()에 Reset(락을 설정하는)기능이 함께 있기에 원자성이 보장되며 ,
class Lock
{
// 생성자의 true : 락 진입 가능 여부
private AutoResetEvent are = new AutoResetEvent(true);
public void Enter()
{
are.WaitOne(); // 락 진입 시도 및 대기
}
public void Exit()
{
are.Set(); // 락 해제
}
}
class Program
{
private static readonly Lock _lock = new Lock();
private const int Count = 100000;
private static int number = 0;
private static void ThreadBody1()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number++;
_lock.Exit();
}
}
private static void ThreadBody2()
{
for (int i = 0; i < Count; i++)
{
_lock.Enter();
number--;
_lock.Exit();
}
}
static void Main(string[] args)
{
Task t1 = new Task(ThreadBody1);
Task t2 = new Task(ThreadBody2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
Console.WriteLine(number);
}
}
- 예상했던 값이 출력되는 것을 확인할 수 있다
Author And Source
이 문제에 관하여([Thread] ManualResetEvent / AutoResetEvent), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@uchang903/Thread-AutoResetEvent저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)