C# ThreadPool Usage
원문 : C# ThreadPool Usage
C# 프로그래밍 언어에서는 thread pool 들을 이용하여 병렬로 작업을 처리한다. ThreadPool 이라는 built-in framework 을 사용하여 (BCL) 작업과 동시에 ProgressBar 를 업데이트 시킬 수 있다.
Understanding ThreadPools
.Net framework 에서는 System.Threading 네임스페이스로 ThreadPool 클래스를 지원한다. 이 클래스는 객체 생성없이 바로 접근할 수 있는 static class 이다. 이 클래스 에서는 thread pool 의 필수적인 부분을 지원해 준다. 또한 이 클래스는 thread pool 패턴을 구현한 클래스 이다. 이는 백그라운드에서 동작하는 많은 분할된 작업을 실행하는데 도움을 준다.
Maximum number of threads
보통 스레드의 최대 개수를 아는 것은 중요하지 않다. .Net ThreadPool 의 전반적인 관점은 ThreadPool 안에서 스레드들의 내부적인 관리이다. Multicore machines 들은 오래된 machines 보다 많은 스레드를 가진다.
cf) ThreadPool에서 사용 가능한 Thread의 개수는?
Microsotf 에서 설명하기로는 thread pools 는 전형적으로 스레드의 최대 개수를 가진다. 만약 모든 스레드가 사용중인 상태일 경우, 추가적인 작업들은 이용 가능한 스레드를 할당 받기 전까지 queue 에 놓여진다.
Usage locations (ThreadPool 의 적절한 사용 위치)
서버와 일괄 처리 응용프로그램이 있다. ThreadPool 은 더 적은 비용으로 스레드를 생성하는 로직을 가진다. 스레드들이 이미 만들어져 있고, 단지 필요할 때 연결만 시켜주면 되기 때문이다. 왜 ThreadPool-Style 코드가 서버에 사용되어지는 이유는 다음과 같다.
MSDN 에 따르면, "서버 응용프로그램에서는 종종 ThreadPool 이 사용되어 진다. 각각의 입력 요청들은 thread pool 의 스레드로 할당되고, 요청들은 비동기적으로 처리된다. primary thread 의 부담을 주지 않고, 순차적인 요청들의 처리를 지연시키지 않으면서." [How to Use a Thread Pool - MSDN]
ThreadPool vs. BackgroundWorker
여러분이 만약 Windows Forms 를 사용한다면, 더 간단한 스레딩 요구를 위해 BackgroundWorker 콤포넌트를 사용하기를 추천한다. BackgroundWorker 는 많은 장점이 있다. 많은 프로세서의 일괄 처리를 위해서는 ThreadPool 이 필요하다. [C# BackgroundWorker Tutorial - dotnetperls.com]
Thread Consideration
- 요구 : 응용프로그램이 일괄 처리(Batch Processing)를 한다.
- 고려 : ThreadPool
- 요구 : 응용프로그램이 3개 이상의 스레드를 만든다.
- 고려 : ThreadPool
- 요구 : 응용프로그램이 Windows Form 을 사용한다.
- 고려 : BackgroundWorker
또한 여러분이 스레드를 어떻게 사용하는지 특성에 따라 최적의 코드를 찾을 수 있다. 다음의 자료는 스레드 시나리오에 의한 비교이다.
- 요구 : 한개의 여분의 스레드를 필요로 한다.
- 사용 : BackgroundWorker
- 요구 : 짧은 시간 사용하는 많은 스레드를 필요로 한다.
- 사용 : ThreadPool
Requirements
threading 은 중요하다. 하지만 긴 실행시간을 갖지 않고, 한번에 한가지 동작을 하는 대부분의 응용프로그램에서는 중요히지는 않다. 또한 인터페이스 이용이 많은 응용프로그램에서는 중요하지 않으므로, 스레드의 사용을 피하라.
Hook up methods (작업과 스레드를 연결하는 메소드)
QueueUserWorkItem 메소드를 사용한다. 스레드에 의해서 처리할 메소드를 가지고 있을 때, 반드시 QueueUserWorkItem 을 이용하여 스레드와 메소드를 연결시켜줘야 한다. 어떻게?? WaitCallBack 를 사용해야 한다. MSDN 에 WaitCallBack 는 ThreadPool 을 실행하였을 때 호출되어지는 delegate callback 메소드라고 설명하고 있다. 즉, callback delegate 이다.
Use WaitCallback
void Example()
{
// Hook up the ProcessFile method to the ThreadPool.
// Note: 'a' is an argument name. Read more on arguments.
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessFile), a);
}
private void ProcessFile(object a)
{
// I was hooked up to the ThreadPool by WaitCallback.
}
실행하기 위한 메소드를 ThreadPool 의 대기 큐에 넣는다. thread pool 의 스레드를 이용 가능할 때 해당 메소드가 실행된다.
보이는것과 같이 첫번째 매개변수로 WaitCallBack 받는다.
WaitCallBack 는 스레드 풀의 스레드에 의해서 실행되어질 callback method 를 나타낸다. 타입에서 알 수 있듯이 delegate 이다.
WaitCallBack 은 void 리턴형과, object 인자를 받는 메소드를 가리킨다.
Use parameters
사용자가 정의한 특별한 클래스를 파라미터로 전달할 수 있다. 특별한 클래스 안에는 사용자의 특별한 값들이 들어있다. 파라미터에는 object 타입을 전달할 수 있으므로 특별한 클래스 객체를 사용할때는 캐스팅 해주어야 한다.
What's going on
위 코드에서 스레드에서 처리할 ProcessFile 메소드로 특별한 클래스 객체를 통하여 2개의 값을 전달하였다. 즉, object 타입 파라미터 이므로 모든 형식을 전달할 수 있다.
Use ProgressBar
스레드풀의 스레드에서 Windows form 안에 사용할 수 있는 ProgressBar 컨트롤에 접근할 수 있다. ProgressBar 의 초기 설정값을 다음과 같이 설정한다.
ProgressBar position.
ProgressBar 의 색깔이 있는 부분의 길이는 Maximum 값의 퍼센테이지 이다. 그래서 만약 Maximum 값이 6 이라면 3의 값일 때 ProgressBar 의 색깔 부분은 반이 채워질 것이다.
Call Invoke on ProgressBar
이번 내용에서는 ProgressBar 인스턴스에 어떻게 Invoke 메소드를 사용하는지에 대해서 알아본다. 불행하게도 worker thread 에서는 Windows Control 에 접근할 수 없다. (windows 의 UI 요소 즉, 컨트롤에 접근할 수 있는 것은 UI 스레드 뿐이며, UI 스레드는 메인 스레드 뿐이다.) worker thread 는 UI 스레드가 아니다. 따라서 ProgressBar 접근을 위해 delegate 와 Invoke 를 사용해야 한다.
Delegate syntax.
위 코드의 시작 부분에 보면 UpdateBar 메소드로 선언된 delegate 를 볼 수 있다. 이상해 보이지만 이 delegate 문법을 사용해야 한다.
More work needed.
위 프로그램은 여러분이 어떻게 ProgressBar 의 Maximum, Minimum 을 설정할 수 있는지 보여주고 있다. 그리고 작업이 끝난 후에 ProgressBar 의 크기를 증가시키기 우해 어떻게 delegate 메소드를 Invoke 시키는지 보여주고 있다.
Threads in debugger
이번 내용은 Visual Studio 2008 debugger 에서 스레드를 볼 수 있는 방법을 설명한다. 여러분의 프로그램이 동작중일 때 스레드를 눈에 볼 수 있게 다음 단계를 거친다.
첫 번째, 응용프로그램을 debug 모드로 연다.
두 번째, 툴바의 초록색 화살표를 클릭하여 디버거를 수행 시킨 다음에, 스레드들이 동작하면 pause 버튼을 누른다.
스레드 창 메뉴의 위치는 다음과 같다.
(디버그 중에 아래와 같은 메뉴가 보인다.)
Constraining worker threads
여러분이 만약 dual-core 또는 quad-core 시스템을 갖고 있다면, 2개 또는 4개의 스레드를 원할것이다. _threadCount 필드를 유지하고, 동작중인 스레드의 개수를 추적하여 스레드 개수를 조절할 수 있다. thread count 필드 값을 사용할 때 필드값이 잘못 읽혀지거나 쓰여지는 것을 피하기 위해 C# 언어에 있는 lock 을 사용해야만 한다. Locks 은 여러분의 스레드가 다른 스레드로 부터 변경 되는 것을 방지해준다.
What we see.
위 코드는 비동기적으로 실행되는 메소드이다. worker thread 의 개수가 4개 보다 작아질때까지 동작하지 않는다. 위 코드는 quad-core machine 에 좋다. lock statement 에 대해서는 다음을 참조 [C# Lock Statement - dontnetperls.com]
Controlling thread counts
threadPool 의 SetMinThreads 메소드를 사용하여 처리량과 성능을 향상시킬 수 있다. 이를 위한 가장 이상적인 최소의 스레드 개수에 대한 참고 자료가 있다. 다음을 참조 [C# ThreadPool.SetMinThreads Method - dontnetperls.com]
Summary
여기서 우리는 C# 프로그램에 많은 스레드들을 효율적으로 관리할 수 있는 threadPool 클래스를 사용할 수 있는 방법을 알아보았다. Windows Form 응용프로그램에서 스레드를 이용한 ProgressBar 와 빠른 UI 변경은 인상적이고 어렵지 않다. 하지만 스레드는 복잡하고 많은 버그를 일으킬 소지가 있다. threadPool 은 매우 간단하다. 하지만 여전히 스레드는 어렵다.
동기화 이벤트
스레드를 활성화 하거나 일시 중단하는 데 사용할 수 있고 신호를 받은 상태 및 신호를 받지 않은 상태 중 한 가지 상태가 지정되는 개체이다. 동기화 이벤트에는 두가지 종류가 있다.
- AutoResetEvent
- ManualResetEvent
둘 사이의 유일한 차이점은 AutoResetEvent 의 경우 스레드를 활성화 할때마다 신호를 받은 상태에서 신호를 받지 않은 상태로 자동으로 변경된다. ManualResetEvent 를 사용하면 신호를 받은 상태를 통해 스레드를 그 수에 상관없이 활성화할 수 있고, 해당 Reset 메소드를 호출한 경우에만 신호를 받지 않은 상태로 되돌릴 수 있다.
WaitHandle
WaitHandle은 공유 자원에 대한 배타적 접근 허용을 위해서 제공되는 클래스다. 다시 말해서, WaitHandle은 커널 객체에 대한 동기화를 제공하는 Win32 API에 대한 래퍼 클래스다. (말이 슬슬 어려워 진다.... ㅎㅎㅎ)
※ 배타적 접근
하나의 스레드가 공유 자원을 사용하고 있으면, 다른 스레드들이 접근하지 못하도록 하는 것을 배타적 접근 이라고 한다.
■ 참조
1. ThreadPool 클래스 - MSDN
2. 방법: 스레드 풀 사용(C# 프로그래밍 가이드) - MSDN
3. C# BackgroundWorker Tutorial - dotnetperls.com
4. C# 쓰레드 이야기 - 12. 식사하는 철학자 - hanb.co.kr
5. [C# Thread]#9. 임계영역