성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>.NET 응용 프로그램에 기본 생성되는 스레드들에 대한 탐구</h1> <p> 이론을 배웠으면, 검증하고 싶은 마음이 생기는데요. ^^ 이번엔, .NET 응용 프로그램에서 생성되는 '기본 스레드들'에 대해서 탐구해 볼까 합니다.<br /> <br /> 복잡한 응용 프로그램에서는 테스트하기가 어려우니까, 간단하게 "Console Application"으로 분석을 해보겠습니다.<br /> <br /> 검증 작업을 돕기 위해 몇 가지 유틸리티 함수가 필요한데요. 우선, 현재 프로그램에 실행 중인 스레드 목록을 다음과 같이 확인해 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void OutputThreads() { Console.WriteLine(); int n = 0; foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) { Console.WriteLine("[" + n + "] " + thread.Id + ", (0x" + thread.Id.ToString("x") + ")"); n++; } } </pre> <br /> 실행해 보면, 다음과 같은 식으로 결과를 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [0] 6712, (<span style='color: blue; font-weight: bold'>0x1a38</span>) [1] 8976, (<span style='color: blue; font-weight: bold'>0x2310</span>) [2] 9244, (<span style='color: blue; font-weight: bold'>0x241c</span>) [3] 2280, (<span style='color: blue; font-weight: bold'>0x8e8</span>) </pre> <br /> 이렇게 실행된 프로세스에 windbg로 연결하면 스레드 목록을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:004> <span style='color: blue; font-weight: bold'>~*</span> 0 Id: 52c.<span style='color: blue; font-weight: bold'>1a38</span> Suspend: 1 Teb: 7efdd000 Unfrozen Start: *** WARNING: Unable to verify checksum for D:\...\Debug\ConsoleApplication1.exe ConsoleApplication1!COM+_Entry_Point <PERF> (ConsoleApplication1+0x2a9e) (011c2a9e) Priority: 0 Priority class: 32 Affinity: ff 1 Id: 52c.<span style='color: blue; font-weight: bold'>2310</span> Suspend: 1 Teb: 7efda000 Unfrozen Start: clr!DebuggerRCThread::ThreadProcStatic (7261b30c) Priority: 0 Priority class: 32 Affinity: ff 2 Id: 52c.<span style='color: blue; font-weight: bold'>241c</span> Suspend: 1 Teb: 7efd7000 Unfrozen Start: clr!Thread::intermediateThreadProc (726fc018) Priority: 2 Priority class: 32 Affinity: ff 3 Id: 52c.<span style='color: blue; font-weight: bold'>08e8</span> Suspend: 1 Teb: 7efaf000 Unfrozen Start: ntdll!TppWaiterpThread (77a541f3) Priority: 0 Priority class: 32 Affinity: ff . <span style='color: blue; font-weight: bold'>4 Id: 52c.128c Suspend: 1 Teb: 7efac000 Unfrozen Start: ntdll!DbgUiRemoteBreakin (77aaf7ea) Priority: 0 Priority class: 32 Affinity: ff</span> </pre> <br /> 원래 4개였다가 windbg를 붙이면서 5개가 되었는데, 마지막 4번 Start 함수의 ntdll!DbgUiRemoteBreakin이라는 이름에서 디버거용 스레드가 해당 프로세스에 생성된 것임을 미뤄 짐작할 수 있습니다. (따라서 4번은 무시하고!)<br /> <br /> 그 외에, 0번은 Console Application의 '주 스레드'임이 확실할 것 같고.<br /> <br /> 남은 것은 1~3번 스레드이지만, SOS 확장 DLL의 도움을 받아서 "!threads" 명령어를 실행해 보면 다음과 같이 2번 스레드가 "Finalizer"라는 것도 알 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:004> !threads ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 1a38 006c7710 a020 Enabled 02822310:02823fe8 006bc7f0 1 MTA <span style='color: blue; font-weight: bold'>2 2 241c 006d25e0 b220 Enabled 00000000:00000000 006bc7f0 0 MTA (Finalizer)</span> </pre> <br /> 자... 이제 남은 것은 1번과 3번 스레드군요.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 사실, 제가 목표한 것은 1번과 3번 중의 하나는 Concurrent GC 스레드일 것이라고 생각했었습니다. 기본적으로 app.config에 아무런 GC 설정 없이 응용 프로그램을 실행하면 적용되는 것이 Concurrent-GC인데, 별도로 2세대 힙만을 전용으로 GC하는 스레드가 존재하는 유형입니다. 이에 대해서는 유경상 님이 쓰신 GC 관련 글에서 다음의 이미지가 잘 나타내 주고 있습니다.<br /> <br /> [Concurrent-GC 모드 (출처: <a target='tab' href='http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx'>http://www.simpleisbest.net/post/2011/04/25/Garbage-Collection-Modes.aspx</a>)]<br /> <img alt='thread_type_1.png' src='/SysWebRes/bbs/thread_type_1.png' /><br /> <br /> 그리고, 또 한 가지는 (제 예상으로) ThreadPool을 담당하는 스레드입니다. 전용 ThreadPool을 만들어 보신 분들은 아시겠지만, ThreadPool을 구현하려면 그것만을 담당하는 스레드 하나가 있어야만 합니다. 그래야, 스레드 풀 내에 maxThreads 수만큼 늘어난 스레드 수가 할 일이 없을 때 다시 minThreads 수로 줄어드는 식의 구현을 할 수 있기 때문입니다.<br /> <br /> 위의 가정을 바탕으로 테스트를 진행해 볼 텐데요. ^^<br /> <br /> 우선, 그중에 하나가 ThreadPool을 관리하는지 테스트해 보려면 어떻게 해야 할까요?<br /> <br /> 제 생각에는, 보통의 상태에서 ThreadPool.QueueUserWorkItem 메서드를 수행하는 것과 1번과 3번 스레드를 강제로 죽인 상황에서 다시 ThreadPool.QueueUserWorkItem을 실행한 경우와 비교해 보면 될 것 같았습니다.<br /> <br /> 그런데, 직접 해보면 2가지 상황 모두 ThreadPool.QueueUserWorkItem으로 정상적으로 새롭게 스레드가 생성이 되고, 심지어 생성된 스레드들은 할 일을 마치고 정확히 20초 후에 종료되는 관리적인 면까지 동일하게 수행되었습니다. 아래는 제가 이에 대해 테스트한 간단한 콘솔 응용 프로그램입니다.<br /> <br /> [정상적인 스레드들이 운용되고 있을 때의 스레드 풀 사용 변화]<br /> <img alt='thread_type_2.png' src='/SysWebRes/bbs/thread_type_2.png' /><br /> <br /> [2개의 스레드를 종료한 이후 스레드 풀을 사용했을 때의 변화]<br /> <img alt='thread_type_3.png' src='/SysWebRes/bbs/thread_type_3.png' /><br /> <br /> 음... 아쉽게도 증명에 실패했습니다. ^^; 일단 더 테스트해 볼 수 있는 시나리오가 생각나지 않으므로 이에 대해서는 넘어가고.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그다음, 1번과 3번 중의 하나는 Concurrent GC용 스레드일 것이라는 가정을 테스트해 보겠습니다.<br /> <br /> 방법은 간단한데요. Concurrent GC 스레드가 2세대 힙을 GC하는 역할을 하기 때문에 1번과 3번 스레드를 모두 죽인 후, 2 세대 GC 카운트가 여전히 발생하는지를 테스트해 보면 됩니다. 코드로는 다음의 2가지 메서드를 상황에 따라 실행하면 되는 작업입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void HeapAllocation() { Console.WriteLine("Old Count: " + OutputGCCount()); for (int i = 0; i < 1000; i++) { byte[] contents = new byte[1024 * 50]; } Console.WriteLine("New Count: " + OutputGCCount()); } static string OutputGCCount() { int gen0 = GC.CollectionCount(0); int gen1 = GC.CollectionCount(1); int gen2 = GC.CollectionCount(2); return ("Gen 0: " + gen0 + ", Gen1: " + gen1 + ", Gen2: " + gen2); } </pre> <br /> 아래는 실제로 제가 테스트해 본 과정입니다.<br /> <br /> <img alt='thread_type_4.png' src='/SysWebRes/bbs/thread_type_4.png' /><br /> <br /> 만약, 그 두 개 중의 하나가 Concurrent-GC였다면 위와 같이 2세대 힙에 대한 GC가 발생하는 일은 없었어야 합니다. (물론, 스레드의 수에 변화가 없이 2세대 힙에 대한 GC가 발생했습니다.) 음... 이번에도 역시 증명에 실패했군요. ^^;<br /> <br /> 혹시, Concurrent-GC를 사용하지 않겠다고 app.config에 명시하면 어떻게 될까요? 초기 스레드 수가 줄어들까요? 확인을 위해 app.config에 다음과 같이 명시해봤습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <?xml version="1.0" encoding="utf-8" ?> <configuration> <runtime> <gcConcurrent enabled="false" /> </runtime> </configuration> </pre> <br /> 위와 같이 설정하게 되면, Non-Concurrent-GC가 동작하게 되고 이렇게 되면 응용 프로그램 내의 스레드가 new를 할 때마다 힙이 부족한 경우, 그런 상황을 유발한 그 스레드를 제외하고 다른 모든 Managed 스레드들은 멈추게 되고 new 할당을 시도하려던 스레드가 직접 GC를 수행하기 때문에 별도의 스레드가 필요가 없어서 예상대로라면 3개의 기본 스레드로 응용 프로그램이 시작되어야 합니다.<br /> <br /> 그러나 실행해 봤지만, 여전히 응용 프로그램 시작 시 4개의 스레드가 생성되는 것에는 변함이 없었습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이 글을 시작할 때만 해도, 4개의 스레드에 대한 모든 정체를 밝히리라 ^^ 다짐했었는데, 결국 2개의 스레드만 정체를 밝히고 나머지는 더욱 혼란만 가중시켜버린 것 같습니다. 혹시, 이 글을 읽는 분들 중에서 나머지 2개에 대한 스레드의 정체를 알고 계신 분이 있거나, 혹은 정체를 밝힐 수 있는 기발한 아이디어가 있다면 덧글 부탁드리겠습니다.<br /> <br /> (위에서 제가 한 테스트를 여러분들도 하실 수 있도록, 첨부 파일에 코드를 포함시켜 두었습니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1157
(왼쪽의 숫자를 입력해야 합니다.)