참고 도서 |
public ManualResetEvent(bool initialState); public AutoResetEvent(bool initialState);initialState는 true나 false를 사용할 수 있으며, 이벤트의 초기상태를 신호상태로 할 것인지, 비신호상태로 할 것인지를 지정한다. 대부분의 경우에 false를 사용한다.
public AutoResetEvent areStep1 = new AutoResetEvent(false); public AutoResetEvent areStep2 = new AutoResetEvent(false); public AutoResetEvent areStep3 = new AutoResetEvent(false);위 예제에서는 false를 사용하기 때문에 비신호상태로 선언한다는 것을 알 수 있다. 비신호상태라는 것은 신호가 될 때까지 신호등 앞에서 기다린다는 의미로 해석하면 된다. 즉, 처음부터 이벤트는 대기상태가 된다. 필자는 쓰레드들의 각각의 작업이 Step1 → Step2 → Step3으로 수행되기를 원한다. 또한 이들 각각의 작업은 데이터를 공유하지 않으며, 단순히 대기중인 쓰레드에게 작업이 끝났다는 사실만을 알려주기 위해 이벤트를 사용한다. 다음은 Step1에 대한 함수이다.
public void Step1() { Console.WriteLine("Processing Step1"); Thread.Sleep(3000); areStep1.Set(); }먼저 화면에 Step1을 처리중이라는 메시지를 출력한다. 그리고 실제로 어떤 작업을 수행하는 함수가 들어가야하지만 여기서는 간단히 어떤 작업을 처리하는 것을 에뮬레이트하기 위해 Thread.Sleep(3000)을 사용하여 3초간 처리중인 것처럼 하였다. 처리가 끝나면 areStep1.Set()을 사용하여 첫번째 이벤트 areStep1을 신호상태로 변경한다. areStep1이 신호상태로 바뀐 것을 감지하고 작업을 수행하는 쓰레드는 Step2이다. 이제 Step2는 어떻게 Step1의 쓰레드가 이벤트를 신호상태로 바꾼 것을 알고, 작업을 처리하는지 살펴보자.
public void Step2() { areStep1.WaitOne(); Console.WriteLine("Processing Step2"); Thread.Sleep(1000); areStep2.Set(); }가장 중요한 부분인데, areStep1.WaitOne()이라고 되어 있다. 즉, 첫번째 이벤트 areStep1이 신호상태가 될 때까지 기다린다는 것을 의미한다. 즉, 쓰레드가 처리하는 어떤 곳에서든지 areStep1이 신호상태가 되면 그것을 감지하고 대기중인 쓰레드를 잠에서 깨우는 역할을 하는 부분이다. Step2에서도 Thread.Sleep(1000) 대신에 DoPerformStep2()와 같이 어떤 함수를 사용할 것이지만 여기서는 작업을 에뮬레이트하기 위해 간단히 Thread.Sleep()을 사용하였다. 마찬가지로 작업이 끝난 다음에 이벤트 areStep2를 신호상태로 변경하여 다른 쓰레드들에게 알린다. Step3은 Step2와 동일하며, 단지 대기중인 이벤트만 다르다.
이름 : event01.cs using System; using System.Threading; public class AppMain { public AutoResetEvent areStep1 = new AutoResetEvent(false); public AutoResetEvent areStep2 = new AutoResetEvent(false); public AutoResetEvent areStep3 = new AutoResetEvent(false); public void Step1() { // do something Console.WriteLine("Processing Step1"); Thread.Sleep(3000); areStep1.Set(); } public void Step2() { areStep1.WaitOne(); Console.WriteLine("Processing Step2"); Thread.Sleep(1000); areStep2.Set(); } public void Step3() { areStep2.WaitOne(); Console.WriteLine("Processing Step3"); areStep3.Set(); } public void DoTest() { Thread thread1 = new Thread(new ThreadStart(Step1) ); Thread thread2 = new Thread(new ThreadStart(Step2) ); Thread thread3 = new Thread(new ThreadStart(Step3) ); Console.WriteLine("Thread 1, 2, 3 are started"); thread1.Start(); thread2.Start(); thread3.Start(); } public static void Main() { AppMain ap = new AppMain(); ap.DoTest(); } }위에서는 AutoResetEvent를 살펴보았다. AutoResetEvent와 ManualResetEvent의 차이점은 여러분이 짐작하고 있는 것처럼 이벤트의 상태가 자동으로 초기화되느냐 그렇지 않느냐의 차이다. 위의 예제에서 Step2를 처리하는 쓰레드는 이벤트가 신호상태가 되기를 대기한다. 신호상태가 되면 이벤트는 쓰레드를 통과시키고 다시 자동으로 비신호상태가 된다. 따라서 직접 Reset()을 사용할 필요가 없다. 반면에 ManualResetEvent는 한 번 신호상태가 되면 다시 Reset()을 명시적으로 호출하여 비신호상태로 만들때까지 계속 신호상태를 유지한다.
public static void Main() { AppMain ap = new AppMain(); ap.DoTest(); AppMain ap2 = new AppMain(); ap2.DoTest(); }이와 같이 변경한 다음에 실행해보면 두 인스턴스의 각각의 쓰레드들이 병렬적으로 실행된다는 것을 알 수 있을 것이다.(전부 몇 개의 쓰레드가 실행중인지 확인하고 싶다면 Thread.Sleep()의 시간을 충분히 늘려놓은 다음에 7회에서 작성한 WinTop을 이용해서 몇 개의 쓰레드가 실행중인지 확인해보는 것도 좋을 것이다) 또는 다음과 같이 Main()을 수정하고 실행해본다.
public static void Main() { AppMain ap = new AppMain(); ap.DoTest(); ap.DoTest(); }즉, 특정 작업이 Step1 → Step2 → Step3으로 병렬적으로 실행되는데 좋다는 것을 알 수 있을 것이다. 대용량의 데이터 다섯 개를 동시에 1M씩 메모리로 읽어들이고, 1M씩 계산하고, 처리된 결과를 저장하는 것과 같은 단계별 작업이 필요하다면 최소한 3개의 쓰레드가 이벤트를 통해서 신호를 주고 받으면서 작업할 수 있을 것이다.(이 경우에 공유 데이터가 없다는 사실에 유의한다.)
public ManualResetEvent mreStep1 = new ManualResetEvent(false); public ManualResetEvent mreStep2 = new ManualResetEvent(false); public ManualResetEvent mreStep3 = new ManualResetEvent(false);ManualResetEvent 역시 AutoEventReset과 동일한 생성자를 갖는다. 여기서도 마찬가지로 세 개의 이벤트를 모두 비신호상태로 둔다. Step1은 다음과 같이 변경한다.
public void Step1() { mreStep1.WaitOne(); mreStep1.Reset(); Console.WriteLine("Processing Step1"); Thread.Sleep(3000); mreStep1.Set(); }WaitOne()으로 이벤트가 신호상태가 되기를 기다리는 코드를 추가하였다. AutoResetEvent와 달리 신호상태에서 하나의 대기 쓰레드를 통과시킨 다음에 자동으로 비신호상태가 되지 않으므로 Reset()을 호출하여 명시적으로 비신호상태로 전환한다.
이름: event02.cs using System; using System.Threading; public class AppMain { public ManualResetEvent mreStep1 = new ManualResetEvent(false); public ManualResetEvent mreStep2 = new ManualResetEvent(false); public ManualResetEvent mreStep3 = new ManualResetEvent(false); public void Step1() { mreStep1.WaitOne(); mreStep1.Reset(); Console.WriteLine("Processing Step1"); Thread.Sleep(3000); mreStep1.Set(); } public void Step2() { mreStep1.WaitOne(); mreStep1.Reset(); Console.WriteLine("Processing Step2"); Thread.Sleep(1000); mreStep2.Set(); } public void Step3() { mreStep2.WaitOne(); mreStep2.Reset(); Console.WriteLine("Processing Step3"); mreStep3.Set(); } public void DoTest() { Thread thread1 = new Thread(new ThreadStart(Step1) ); Thread thread2 = new Thread(new ThreadStart(Step2) ); Thread thread3 = new Thread(new ThreadStart(Step3) ); thread1.Start(); thread2.Start(); thread3.Start(); Console.WriteLine("Thread 1, 2, 3 are started"); } public static void Main() { AppMain ap = new AppMain(); ap.DoTest(); } }위 코드를 컴파일하고 실행하면 아무것도 실행되지 않는다는 것을 알 수 있다. Step1에서도 mreStep1.WaitOne()으로 첫번째 이벤트 mreStep1이 신호되기를 기다리고 있기 때문이다. 따라서 DoTest를 다음과 같이 수정한다.
public void DoTest() { Thread thread1 = new Thread(new ThreadStart(Step1) ); Thread thread2 = new Thread(new ThreadStart(Step2) ); Thread thread3 = new Thread(new ThreadStart(Step3) ); thread1.Start(); thread2.Start(); thread3.Start(); mreStep1.Set(); Console.WriteLine("Thread 1, 2, 3 are started"); }이벤트를 신호상태로 만들어준다. 컴파일하여 실행하면 AutoResetEvent를 사용한 것과 차이가 없을 것이다.(내부적으로는 어떻든간에 말이다)
이전 글 : 삼바를 이용하여 윈도우측 프린터 사용하기
다음 글 : PHP 애플리케이션 분석을 통한 성능 향상
최신 콘텐츠