성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <br /> <div class='mainCenterTitle'>Foreground Thread / Background Thread</div><br /> <br /> 마침 재미있는 글이 또 하나 떠서 ^^ 써봅니다.<br /> <br /> <pre class='code'> 메인 스레드가 종료되면 나머지 스레드도 종료될까? ; <a target='_tab' href='http://www.jiniya.net/tt/526'>http://www.jiniya.net/tt/526</a> </pre> <br /> 실제로 그렇지요. main thread라고 해서 특별한 것은 없습니다. 단지 해당 프로세스 공간의 Code Section을 긁고(!) 다니는 첫 번째 스레드일 뿐. (스레드라... 갑자기 <a target='_tab' href='/0/0/43'>안 좋은 기억</a>이 떠오르는군요. 그 2명도... 어쩌면 "<a target='_tab' href='http://blog.naver.com/saltynut/120039707278'>네이버 프로그래머</a>"였을 지도.)<br /> <br /> 그런데, 닷넷에서 제공되는 Thread 클래스에는 재미있는 속성이 하나 있습니다. 바로 "IsBackgroundThread (read/write)"라는 속성입니다.<br /> <br /> Foreground Thread(전경 스레드)와 Background Thread(배경 스레드)의 차이점은 단 하나입니다. Foreground Thread의 경우에는 아직 Running 상태에 있다면, 프로세스가 종료되는 것을 막는 반면, Background Thread의 경우에는 설령 동작 중이라 해도 프로세스의 종료를 막지 못합니다.<br /> <br /> 오호... 스레드에는 주종 관계가 없다고 위의 글에도 나와 있는데... 그렇다면 이를 어찌 설명해야 할까요?<br /> <br /> 그렇습니다. 닷넷의 "Managed" 환경은, 스레드까지도 잘 "관리"를 해주기 때문에 이런 것이 가능합니다. 이에 대한 대강의 해답을 찾기 위해서는... 역시나 "Shared Source CLR"을 참고하는 것이 좋겠습니다.<br /> <br /> <pre class='code'> Shared Source Common Language Infrastructure 2.0 Release ; <a target='_tab' href='http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en'>http://www.microsoft.com/downloads/details.aspx?FamilyId=8C09FD61-3F26-4555-AE17-3121B4F51D4D&displaylang=en</a> </pre> <br /> 그럼, 단서가 되는 부분을 하나씩 검색해 볼까요?<br /> <br /> "찾기"를 이용해서 우선 검색해 보아야 할 것이 "IsBackground"의 정의일 테죠! 오호... 검색해 보니 아래와 같이 나옵니다.<br /> <br /> <pre class='code'> \sscli20\clr\src\bcl\system\threading\thread.cs public bool IsBackground { get { return IsBackgroundNative(); } [HostProtection(SelfAffectingThreading=true)] <b>set { SetBackgroundNative(value); }</b> } [MethodImplAttribute(MethodImplOptions.InternalCall)] private extern bool IsBackgroundNative(); [MethodImplAttribute(MethodImplOptions.InternalCall)] <b>private extern void SetBackgroundNative(bool isBackground);</b> </pre> <br /> 차례로, "SetBackgroundNative"를 검색해 보면.<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\ecall.cpp FCFuncStart(gThreadFuncs) <b>FCFuncElement("SetBackgroundNative", ThreadNative::SetBackground)</b> </pre> <br /> 결국 실제 정의는 "ThreadNative::SetBackground"에 있다는 것이고. 이를 또 검색해 보면.<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\comsynchronizable.cpp FCIMPL2(void, ThreadNative::SetBackground, ThreadBaseObject* pThisUNSAFE, CLR_BOOL isBackground) { ; 생략 // validate the thread Thread *thread = pThisUNSAFE->GetInternal(); ; 생략 <b>thread->SetBackground(isBackground);</b> HELPER_METHOD_FRAME_END(); } FCIMPLEND </pre> <br /> 이렇게 Thread 클래스의 SetBackground 메서드에 isBackground 메서드를 전달하는 것으로 끝을 맺고 있습니다. 그렇다면 과연 SetBackground에서는 어떤 역할을 할까요?<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\threads.cpp // Background threads must be counted, because the EE should shut down when the // last non-background thread terminates. But we only count running ones. void Thread::SetBackground(BOOL isBack) { ; 생략 if (IsDead()) { } else if (isBack) { if (!IsBackground()) { FastInterlockOr((ULONG *) &m_State, TS_Background); // unstarted threads don't contribute to the background count if (!IsUnstarted()) ThreadStore::s_pThreadStore->m_BackgroundThreadCount++; } } else { ; 생략 } } </pre> <br /> 자... "Thread::SetBackground" 함수까지 오니 이제서야 쓸만한 코드들이 나오기 시작합니다. 닷넷에서는, 명시적으로 IsBackground 값을 True로 지정하지 않는 한은 Foreground 스레드입니다. 따라서 isBack == TRUE이고 현재 Background 스레드가 아니면 s_pThreadStore->m_BackgroundThreadCount 값을 1 증가시켜 놓고 있습니다.<br /> <br /> 그렇다면, 간단한 상황이라고 가정하고 - 현재 어떤 스레드가 종료 중일 때 총 구동되고 있는 Managed Thread 수와 m_BackgroundThreadCount - 1의 수가 같은 상황이라면 주저없이 프로세스 종료를 해도 상관없다는 결론이 나옵니다. 즉, 닷넷의 CLR 호스팅 코드에서는 이러한 처리가 되어 있다는 것이겠지요.<br /> <br /> 예상 결과를 검증해 보기 위해 조금 더 검색을 해보면, CLR 종료를 할 수 있는 이벤트를 발생하는 다음과 같은 코드를 발견할 수 있습니다.<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\threads.cpp void ThreadStore::CheckForEEShutdown() { ; 생략 if (g_fWeControlLifetime && <b>s_pThreadStore->OtherThreadsComplete</b>()) { BOOL bRet; bRet = <b>s_pThreadStore->m_TerminationEvent.Set();</b> _ASSERTE(bRet); } } </pre> <br /> 아하... 예상이 맞아 들어갑니다. OtherThreadsComplete 조건이 TRUE가 되면 m_TerminationEvent 이벤트가 signal 되는군요. 잠시 OtherThreadsComplete 함수를 찾아보면,<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\threads.h <b>// Have all the foreground threads completed? In other words, can we release // the main thread?</b> BOOL OtherThreadsComplete() { ; 생략 return (m_ThreadCount - m_UnstartedThreadCount - m_DeadThreadCount - Thread::m_ActiveDetachCount + m_PendingThreadCount == m_BackgroundThreadCount); } </pre> <br /> return 코드는 볼 것도 없이, ^^; 주석이 잘 말해주고 있습니다. 그렇다면, 이렇게 조건을 만족해서 m_TerminationEvent가 signal 된다면 어떤 코드가 실행이 되는 것일까요?<br /> <br /> 아래의 코드에서 그 해답을 찾을 수 있습니다.<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\threads.cpp void ThreadStore::WaitForOtherThreads() { ; 생략 Thread *pCurThread = GetThread(); ; 생략 if (!OtherThreadsComplete()) { TSLockHolder.Release(); FastInterlockOr((ULONG *) &pCurThread->m_State, Thread::TS_ReportDead); DWORD ret = WAIT_OBJECT_0; <b>while (CLREventWaitWithTry(&m_TerminationEvent, INFINITE, TRUE, &ret)) { }</b> _ASSERTE(ret == WAIT_OBJECT_0); } } </pre> <br /> 무한(INFINITE) 대기를 하고 있습니다. 만약 이 함수의 while 루프가 종료하게 되면... 아마도 당연히 CLR 호스팅이 종료하게 될 것입니다. 그래서 찾아보면 다음의 코드를 발견할 수 있습니다.<br /> <br /> <pre class='code'> \sscli20\clr\src\vm\assembly.cpp static void RunMainPost() { ; 중간 생략 GCX_PREEMP(); <b>ThreadStore::s_pThreadStore->WaitForOtherThreads();</b> #ifdef _DEBUG // Turn on memory dump checking in debug mode. _DbgRecord(); #endif } </pre> <br /> RunMainPost가 리턴하면, Assembly::ExecuteMainMethod가 종료하고, 이어서 SystemDomain::ExecuteMainMethod, ExecuteEXE, _CorExeMain2까지 이어지고, 결국 EEPolicy::HandleExitProcess를 호출하는 것으로 CLR 호스트 프로세스는 종료하게 됩니다.<br /> <br /> 오호... 그런데... 이 순서를 어디선가 본 기억이 나는 것 같습니다.<br /> 기억하시는 분이 계실래나? ^^<br /> <br /> <pre class='code'> <b>0:000> k</b> ChildEBP RetAddr 0026ed70 79f9691a KERNEL32!RaiseException+0x58 0026edd0 7a098e88 mscorwks!RaiseTheExceptionInternalOnly+0x226 0026ee94 003e00a7 mscorwks!JIT_Throw+0xfc WARNING: Frame IP not in any known module. Following frames may be wrong. 0026eeb0 79e826bd 0x3e00a7 0026ef30 79e8451b mscorwks!CallDescrWorkerWithHandler+0xa3 0026f06c 79e84403 mscorwks!MethodDesc::CallDescr+0x19c 0026f084 79e843e0 mscorwks!MethodDesc::CallTargetWorker+0x20 0026f098 79ef3e20 mscorwks!MethodDescCallSite::CallWithValueTypes+0x18 <b>0026f1fc 79ef3c19 mscorwks!ClassLoader::RunMain+0x220</b> 0026f464 79ef3aee mscorwks!Assembly::ExecuteMainMethod+0xa6 0026f934 79ef3737 mscorwks!SystemDomain::ExecuteMainMethod+0x398 0026f984 79ef18d1 mscorwks!ExecuteEXE+0x59 0026f9cc 79003aa0 mscorwks!_CorExeMain+0x11b 0026f9dc 76763833 mscoree!_CorExeMain+0x2c 0026f9e8 77c7a9bd KERNEL32!BaseThreadInitThunk+0xe 0026fa28 00000000 ntdll!_RtlUserThreadStart+0x23 </pre> <br /> 기억이 안 나신다고요? ^^ <br /> 다음의 토픽에서 설명했었습니다. (그나저나,,, windbg는 자주 사용을 안하다 보니 잊어버리게 되는군요. ^^;)<br /> <br /> <pre class='code'> (Managed) Main Method에 Break Point 걸기 ; <a target='_tab' href='/2/0/469'>http://www.sysnet.pe.kr/2/0/469</a> </pre> <br /> 자꾸만 연상되는군요. 그러고 보니... <a target='_tab' href='/2/0/492'>SQLClr에서는 위의 호스팅 코드에서 사용되던 TLS도 모두 FLS</a>로 바꾸는 작업을 했겠지요?<br /> <br /> 끝으로. 역시나,,, 이번에도 <a target='_tab' href='http://www.jiniya.net/'>신영진</a> 님 덕분에. ^^<br /> <br /> <hr style='width: 50%' /><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;' > #include <thread> #include <iostream> #include <unistd.h> #include <pthread.h> #define USE_STL void* threadFunc(void* arg) { sleep(5); std::cout << "Hello from thread!" << std::endl; return NULL; } int main() { #if defined(USE_STL) std::thread t([&]() { sleep(5); std::cout << "Hello from thread!" << std::endl; }); t.detach(); #else pthread_t tid; pthread_create(&tid, NULL, threadFunc, NULL); #endif pthread_exit(nullptr); return 0; } </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include <thread> #include <iostream> #include <Windows.h> DWORD threadFunc(void* arg) { Sleep(5000); std::cout << "Hello from thread!" << std::endl; return 0; } int main() { #if defined(USE_STL) std::thread t([&]() { Sleep(5 * 1000); std::cout << "Hello from thread!" << std::endl; }); t.detach(); #else CreateThread(nullptr, 0, threadFunc, nullptr, 0, nullptr); #endif ExitThread(0); return 0; } </pre> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4681
(왼쪽의 숫자를 입력해야 합니다.)