성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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'>Native 스레드와 Managed 스레드 개체의 상태 관계</h1> <p> 네이티브에서는 CreateThread Win32 API로 얻은 HANDLE 개체를 이용해서 스레드를 제어할 수 있고, 닷넷에서는 그와 같은 역할을 System.Threading.Thread 타입을 이용해서 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CreateThread function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread</a> Thread Class ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread'>https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread</a> * 대부분의 닷넷 타입들이 static 메서드에 대해서만 "Thread Safety"한데, Thread 타입의 경우 드물게 인스턴스 메서드에 대해서도 "Thread Safety"한 특징이 있습니다. </pre> <br /> 실제로 지난번 글에서 Thread 인스턴스에서는 Native Thread Id를 구해오는 것조차 쉽지 않음을 보여드렸는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET System.Threading.Thread 개체에서 Native Thread Id를 구할 수 있을까? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1244'>http://www.sysnet.pe.kr/2/0/1244</a> </pre> <br /> 그럼에도 불구하고 제가 스레드의 Naitve/Managed 상태에 대해서 관심을 갖게 된 것은 ".NET에서 다른 스레드의 콜 스택 덤프"를 뜨는 방법을 찾는 것에서 시작되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 스레드 콜 스택 덤프 (1) - 다른 스레드의 스택 덤프 구하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/802'>http://www.sysnet.pe.kr/2/0/802</a> </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;' > .NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1162'>http://www.sysnet.pe.kr/2/0/1162</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > bool suspend = false; if ((newThread.ThreadState & System.Threading.ThreadState.Suspended) != System.Threading.ThreadState.Suspended) { newThread.Suspend(); suspend = true; } DoCallStack(); if (suspend == true) { newThread.Resume(); } </pre> <br /> if 문을 실행했을 때와 Suspend를 실행한 시점이 하나의 동기화 단위로 묶이지 않기 때문에 상태가 엇갈릴 수 있음을 설명드렸습니다. <br /> <br /> 이번 글은, 위의 문제를 해결하려고 Native 스레드를 건드리면서 알게 된 정보를 공유해 보는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 아실 지 모르지만, Win32 API에서의 Resume/Suspend 메서드는 호출 횟수가 카운팅이 된다는 특징이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ResumeThread function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread</a> SuspendThread function ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread'>https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-suspendthread</a> </pre> <br /> 즉, SuspendThread를 2번 호출했으면 ResumeThread를 2번 호출해주어야만 정상적으로 스레드가 Running 상태로 복귀하는 것입니다.<br /> <br /> 여기에서 힌트를 얻은 것이, 그렇다면 닷넷 스레드 타입에서 Suspend/Resume를 해줄 것이 아니라 Native 차원에서 해주면 혹시 '동기화 단위'로 묶이지 않은 것에 대한 문제를 해결할 수 있지 않을까... 하는 생각을 하게 된 것입니다.<br /> <br /> 하지만, 결론부터 말씀드리자면 ^^ 이것은 완전히 빗나간 예측입니다. 닷넷은, CLR 위에서 돌아가는 것이니만큼 너무나 추상화를 잘해주어서 Managed 차원에서의 Suspend/Resume은 Native 차원에서의 Suspend/Resume과 완전히 상관이 없기 때문입니다.<br /> <br /> 이에 대해 간략하게 예제 코드로 테스트 해볼까요? ^^<br /> <br /> 우선, <a target='tab' href='http://www.sysnet.pe.kr/2/0/1244'>이전 글에서 사용한 방법을 통해서 다른 스레드의 Native Thread Id</a>를 구하는 것에서부터 시작합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > bool net45 = false; Type type = Type.GetType("System.Reflection.ReflectionContext"); if (type != null) { net45 = true; } Thread newThread = new Thread(Run); newThread.Start(); FieldInfo fieldInfo = typeof(Thread).GetField("DONT_USE_InternalThread", BindingFlags.NonPublic | BindingFlags.Instance); IntPtr objValue = (IntPtr)fieldInfo.GetValue(newThread); // .NET 4.0에서는 offset 값이 16이었지만, .NET 4.5를 설치하면 15로 바뀜. IntPtr teb = new IntPtr(Marshal.ReadInt32(objValue, ((net45 == true) ? 15 : 16) * 4)); <span style='color: blue; font-weight: bold'>int clientTid = Marshal.ReadInt32(teb, 0x6b8);</span> </pre> <br /> 세상에나... 지난번에도 말씀드렸지만 offset 값을 쓰는 것이 저렇게 위험하답니다. ^^ (.NET 4.5에서는 옵셋값이 60바이트 위치로 바뀐 것을 확인할 수 있습니다.)<br /> <br /> Run 메서드는 다음과 같이 일단 무한 루프를 돌도록 만들었습니다.<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 Run() { int idx = 0; <span style='color: blue; font-weight: bold'> while (true) { Interlocked.Increment(ref idx); // 동기화가 필요없지만... 그냥! System.Diagnostics.Trace.WriteLine(idx); }</span> } </pre> <br /> 자, 이제 스레드 상태를 알 수 있는 함수를 작성해야 하는데요. 우선, Managed 개체에서는 단순히 Thread.ThreadState 속성을 제공해주는 것으로 해결이 되지만, Native 스레드 상태를 알기 위해서는 다음과 같이 native thread id를 단서로 알아내는 코드를 작성할 수 있습니다.<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 CheckThreadState(int clientTid) { foreach (<span style='color: blue; font-weight: bold'>ProcessThread thread in Process.GetCurrentProcess().Threads</span>) { if (thread.Id == clientTid) { Console.WriteLine("[" + clientTid + " thread] : " + thread.ThreadState); break; } } } </pre> <br /> 준비는 끝났고, 이제 프로그램을 실행하고 CheckThreadState(clientTid); 코드를 이용해서 상태를 확인하면 Native 스레드는 "Running" 중임을 알 수 있습니다.<br /> <br /> 그럼 SusnepdThread 2번에 ResumeThread 한 번을 호출하고 스레드 상태를 확인해 볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)clientTid); if (threadHandle == IntPtr.Zero) { break; } CheckThreadState(clientTid); // Running <span style='color: blue; font-weight: bold'>SuspendThread(threadHandle); SuspendThread(threadHandle); ResumeThread(threadHandle);</span> CheckThreadState(clientTid); // Wait </pre> <br /> 아하... Wait 상태인 것으로 봐서 Suspend/Resume 짝이 맞아야 한다는 것을 테스트로 알 수 있습니다. 그래서, ResumeThread를 한번 더 호출해 주고 CheckThreadState로 확인하면 Running으로 돌아오는 것을 알 수 있습니다.<br /> <br /> 그럼, 이 상태에서 Managed 스레드 개체의 상태를 함께 출력해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Console.Write("System.Threading.Thread.ThreadState == " + newThread.ThreadState + ", "); // Running CheckThreadState(clientTid); // Running SuspendThread(threadHandle); SuspendThread(threadHandle); ResumeThread(threadHandle); Console.Write("System.Threading.Thread.ThreadState == " + newThread.ThreadState + ", "); // Running CheckThreadState(clientTid); // Wait </pre> <br /> 그렇습니다. Managed 스레드 상태는 Native 스레드 상태에 관계없이 언제나 Running 상태만을 표시하고 있습니다. 즉, Native와 Managed 간에 상태가 완전히 독립적이라는 것을 알 수 있습니다. <br /> <br /> 반대의 경우는 어떨까요? System.Threading.Thread.Suspend()를 호출한 경우, Managed 스레드 상태를 바꾸긴 하지만 실제로 그 실행을 중지시키려면 Native 스레드를 중지시켜야 하기 때문에 이번에는 정상적으로 2개의 상태가 Suspended / Wait를 각각 가리키게 됩니다.<br /> <br /> 이 때문에 Native 스레드에서 SuspendThread를 호출해 주었다고 해도 콜스택을 뜨는 System.Diagnostics.StackTrace 함수에서 확인하는 Managed 스레드의 상태가 여전히 Running이므로 ThreadStateException 예외가 발생하게 되는 것입니다.<br /> <br /> 결국 정리해 보면... System.Diagnostics.StackTrace를 이용하여 다른 Managed 스레드의 콜스택을 안전하게 얻으려면 System.Threading.Thread 개체 차원에서도 Native와 마찬가지로 Suspend/Resume에 대한 카운트가 제공되어야만 합니다. (아쉽게도, 테스트 해본 결과 System.Threading.Thread는 Suspend/Resume에 대한 카운트 관리 기능은 없습니다.)<br /> <br /> 어쨌든 현재로써는, 제가 아는 한 다른 스레드의 콜 스택을 얻는 가장 안전한 방법은 ICorDebug를 사용하는 방법뿐이 없는 것 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1249'>http://www.sysnet.pe.kr/2/0/1249</a> </pre> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=706&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1868
(왼쪽의 숫자를 입력해야 합니다.)