Microsoft MVP성태의 닷넷 이야기
.NET Framework: 89. ManagedThreadId - 두 번째 이야기 [링크 복사], [링크+제목 복사]
조회: 11231
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

ManagedThreadId - 두 번째 이야기


[이 토픽은 이전 "ManagedThreadId ?" 글에 달린 댓글에 대해 답변으로 쓴 것입니다.]

물론, ManagedThreadId 가 Thread.GetHashCode 의 래퍼에 불과하긴 하지만. 관리 스레드와 Native 스레드를 구분해야 하는 이유가 Fiber 의 도입 여부에 의존한다는 것은 간과할 수 없는 사실로 보입니다. 실제로 Obsolete 에 나온 경고문 이나, MSDN 도움말에 나온 ManagedThreadId 의 설명을 보아도 그렇습니다.

.NET Framework Class Library  - Thread.ManagedThreadId Property  
; http://msdn2.microsoft.com/en-us/library/system.threading.thread.managedthreadid(VS.80).aspx

The value of the ManagedThreadId property does not vary over time, 
even if unmanaged code that hosts the common language runtime implements the thread as a fiber.

말씀하신 내용 중에,

"
굳이 Fiber를 운운하지 않더라도 호스팅 환경에 따라서 서로 다른 두개 이상의 운영체제 스레드가 하나의 Thread 객체에 매핑 될 수 있기 때문입니다.
"



위와 같이 언급하신 것이 있는데, 제 생각에는 .NET 1.x 의 그 문서 내용을 너무 확장해서 판단하신 것이 아닌가 생각됩니다. 현실적으로 볼때 "서로 다른 두개 이상의 운영체제 스레드가 하나의 .NET Thread 객체에 매핑" 되는 것은 불가능하기 때문입니다. 만약, 그러한 매핑이 가능하다면 (ThreadStatic 특성이 적용된 것을 포함해서) 여전히 PInvoke 로 호출되는 하부 자원들에서 사용되고 있을지도 모르는 TLS 는 어떻게 관리가 되는 것인지요?

ICLRTask, ICLRTaskManager 등의 인터페이스가 없는 .NET 1.x 에서는 그러한 호스팅을 구현하는 것이 불가능했으며, .NET 2.0 에 와서야 그러한 인터페이스가 구현되어 CLR 호스팅을 커스터마이징할 수 있는 기회가 생기긴 했지만, 그 조차도 TLS 를 사용하는 부분에 대해서 FLS(Fiber local storage)를 사용하도록 바꿔주는 구현이 필요했는데, .NET 1.x 에서는 FLS 관련한 코드가 전혀 없기 때문에 "향후의 CLR 버전의 가능성"이 그랬을 뿐, 실제로 1.x 에서 가능했던 것은 아닙니다. 그리고, .NET 2.0 을 기반으로 한 SQLClr 에서 실제로 그러한 구현을 시도했지만, 잘 아시는 것처럼 실제 릴리즈 버전에서는 제외되었고요.

설령 FLS를 쓴다고 해도 Fiber 자체가 thread-affinity 이기 때문에 FLS 자체가 서로 다른 Native 스레드에서 접근할 수 있는 배려가 전혀 안되어 있습니다.




그 다음... ^^ 아래의 의견에 대해서 말씀드리면,

"
1.x 시절부터 닷넷 환경에서 운영체제의 thread id는 신뢰할 수 없는 값이였습니다.
"



Fiber 구현 여부를 제외한다면, .NET 1.x/2.0 에서 GetCurrentThreadId 와 Thread.GetHashCode 를 구분할 필요는 없다고 보입니다. 즉, 여전히 신뢰할 수 있는 값입니다. 그렇지 않다면 Obsolete 수준이 아니라 아예 사용할 수 없도록 만들어야 했습니다.

좀더 자세하게 살펴본다면, 해당 논리 스레드(.NET Managed Thread)가 일단 생성된 이후부터는 철저하게 하나의 동일한 Native 스레드에 매핑되어집니다. 위에서 이미 강조해서 말씀드렸지만, TLS 를 사용하는 여러 기반 환경으로 인해 관리 스레드를 여러개의 Native 스레드에서 스케쥴링하는 것은 불가능합니다.

하나의 관리 스레드가 생성되어 그 ID 가 3 이고, 이때 Native 스레드 ID 는 3000 이었다고 가정해 보겠습니다. .NET 1.x 는 물론이고, 2.0 현재의 버전에서도 TLS 로 인해 절대로 관리 스레드(3) 를 수행하는 도중, (Suspend 후 Resume 을 포함해서) Native 스레드의 ID 가 3000 이외의 값이 나오는 경우는 없습니다.

나아가서, 관리 스레드가 종료된 후 GC 까지 구동되어 완전하게 관리 스레드 자원이 해제된 경우에는 새로 생성되는 관리 스레드에 대해서 이전의 관리 스레드 ID 3 의 경우 및 Native 스레드 ID 3000 이 나오는 것이 가능합니다. 결국 스레드가 종료된 이후에는 관리 스레드 ID 나 Native Thread ID 나 모두 해당 숫자가 재사용된다는 것에는 변함이 없습니다.




현재까지는, 순전히 Fiber 의 도입 문제로 인해 ManagedThreadId 가 필요한 것을 알 수 있습니다. 하나의 Native 스레드에서 여러 개의 Fiber 가 묶여지다 보니, 각각의 Fiber 에서 스케쥴링 되는 관리 스레드들이 자신의 고유 스레드 ID 가 필요하게 되었고, 그런 이유로 인해 .NET 에서 ManagedThreadId 가 제공되는 것이 의미가 있게 되는 것입니다. 물론... Fiber 가 없다면, ManagedThreadId 를 쓰나 GetCurrentThreadId 를 쓰나 해당 스레드를 구분하는 고유 ID 로써 사용하는 것에는 무리가 없고요.

이러 저러한 이야기를 종합할 때. 단순히 1.x 의 Thread.GetHashCode 가 Thread.ManagedThreadId 로 온 것에 불과하지만, 실제 뒷 이야기는 Fiber 와 관련되어 있다는 것을 "ManagedThreadId ?" 토픽에서 써본 것이었습니다.

그 외, Fiber 와 관련된 블로그 토픽을 조금 소개해 봅니다.

Fibers and the CLR 
; http://www.bluebytesoftware.com/blog/CommentView,guid,2d0038b5-7ba5-421f-860b-d9282a1211d3.aspx

AppDomain.GetCurrentThreadId() and ManagedThreadId are different 
; http://www.thescripts.com/forum/thread440146.html

How to get thread id for a managed thread?
; http://blogs.msdn.com/junfeng/archive/2004/02/03/66502.aspx

참고로, 관리 스레드가 여전히 TLS 등의 문제로 인해 발생할 수 있는 문제점을 해결하기 위해 다음과 같은 함수들이 ManagedThreadId 와 함께 .NET 2.0 에 추가되었고요. (물론, 현재는 이런 함수들의 호출 여부가 전혀 영향을 주지 않고 있지만.)

Thread.BeginThreadAffinity Method  
; http://msdn2.microsoft.com/en-au/library/system.threading.thread.beginthreadaffinity.aspx

Thread.EndThreadAffinity Method  
; http://msdn2.microsoft.com/en-au/library/system.threading.thread.endthreadaffinity.aspx

제 생각이 틀렸나요???
의견을 부탁드립니다. ^^ 말씀하신 것처럼, 좋은 토론이 되지 않나 싶습니다.



[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 4/25/2019 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 



2007-05-24 03시57분
[Loner] 하하... 성태씨한테는 못당하겠군요... 대단하십니다... ^^
먼저 제가 두개 이상의 운영체제 쓰레드가 하나의 Managed Thread와 매핑될 수 있다는
결론을 내리게 된 배경을 설명하지요.

사실, ASP.NET 과 같이 쓰레드 풀을 사용하면서 다수의 AppDomain을 호스팅하는
환경에서 하나의 운영체제 쓰레드가 여러 AppDomain을 서비스 할 수 있을 겁니다.
이 경우, 하나의 운영체제 쓰레드는 두 개 이상의 managed thread 객체에 매핑되지
않나요? 즉, GetCurrentThreadId가 같은 값을 반환하지만 두 Thread 인스턴스는
같지 않게 되죠. 제가 틀렸나요?

이 때문에 1.x 시절부터 GetCurrentThreadId를 100% 신뢰할 수 없다는 생각을
갖었었고, 1.x의 문서에도 GetHashCode를 사용하는 것이 보다 명확하다고 명시되어 있습니다.
(http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemthreadingthreadclasstopic.asp)
다음은 위 문서의 내용중 발췌한 것입니다.

"GetHashCode 는 관리되는 스레드에 대한 ID를 제공합니다. 스레드는 수명 동안, 값을 가져오는 응용 프로그램 도메인에 관계없이 다른 스레드의 값과 충돌하지 않습니다.

참고 관리되지 않는 호스트는 관리되는 스레드와 관리되지 않는 스레드 간의 관계를 제어할 수 있으므로 운영 체제 ThreadId는 관리되는 스레드와 고정 관계가 없습니다. 특히, 정교한 호스트에서는 CLR 호스팅 API를 사용하여 동일한 운영 체제 스레드에 대해 관리되는 여러 스레드를 예약하거나 여러 다른 운영 체제 스레드 사이로 관리되는 스레드를 이동할 수 있습니다."

이 1.x의 MSDN 문서에 의하면 하나의 관리되는 쓰레드가 여러 운영체제 쓰레드와 매핑될 수
있다고 나와 있기 때문에 그런 생각을 갖게 되었죠.
물증도 없이 심증으로만 그런 결론을 내린 저의 접근방법이 옳지 않다는 것은 인정합니다.
하지만 재현이 무쟈게 어렵고 귀찮기 때문에 MSDN을 그대로 믿었다는...

그리고 성태씨 말대로 TLS를 사용하는 Managed thread에 대한 정보를 고려해 보면
두 운영체제 쓰레드가 하나의 Managed thread에 매핑되는 상황은 거의 일어날 수
없을 듯 합니다만... 다시 한번 핑계를 대자면 MSDN 문서에 명시되었던 내용인지라...
그냥 그런가 보다 하고 받아들였던 것으로 기억이 납니다.

GetCurrentThreadId가 Obsolete 수준으로 끝난 이유는 하위 호환성 때문이지,
신뢰할 수 있기 때문이 아니라고 봅니다. 1.x 부터 GetCurrentThreadId는
운영체제 쓰레드의 Id 이지 닷넷 환경의 논리 쓰레드의 Id와는 같지 않기 때문이지요.
이는 1.x 시절부터 이어진 상황인 것만은 확실하며, 정말로 정확하게 따져보자면
운영체제의 ThreadId와 ManagedThreadId는 일대일 매핑이 되지 않는다고 봐야할 듯
싶네요. 그것이 Fiber와 연관이 있건 없건 말이지요.
GetHashCode로서 Managed Thread에 대한 Id로서 사용하는 것이
불합리하다고 판단되어 ManagedThreadId 속성이 추가된 것으로 저는 알고 있으며
Fiber를 위해서만은 아니라고 여전히 생각됩니다.

ManagedThreadId는 GetHashCode 처럼 변화하지 않는 값이 맞습니다.
하나의 Thread 인스턴스는 항상 고유의 ManagedThreadId 값을 반환하겠지요.
닷넷 프로그램은 이 Id 값에 의해 Managed thread를 구분하면 안전할 겁니다.
하지만 동일한 ManagedThreadId 값을 반환했다고 해서 GetCurrentThreadId 가
같은 값을 반환할 것이라고는 100% 장담할 수 없다는 것이 저의 생각입니다.

실제에서는 GetCurrentThreadId와 ManagedThreadId를 구분해야 하는 경우는 99.99%
발생하지 않으므로 저도 실제 코드를 작성할 때는 신경을 쓰지 않습니다.
저는 오히려 GetCurrentThreadId 의 값을 더 선호하는데요, 다양한 unmanaged 디버깅
도구를 사용할 때 편하기 때문이죠. 컴파일러가 경고를 내는 것이 보기 싫어서
P/Invoke 선언을 직접 해서도 사용하기까지 합니다.

의견 주세요. ^^
[손님]
2007-05-25 08시21분

답변이 늦었습니다. ^^

저는 쓰레드 풀을 "관리 쓰레드 : Native 쓰레드" 가 1:1로 매핑된 집합으로 여기고 있습니다.

말씀하신 "Thread 관련 웹 문서" 에 따르면,

a sophisticated host can use the CLR Hosting API to

(1) schedule many managed threads against the same operating system thread,
(2) move a managed thread between different operating system threads.

위와 같은 내용이 나오는데요. 일단, (1)번의 경우에 대한 제 생각은 순전히 fiber 의 도입만이 가능한 것일 뿐, fiber 가 아닌 다음에는 동일한 OS Thread 를 다수의 관리 쓰레드에 매핑하는 것은 "현실적으로 불가능"하다고 보입니다. (사실 Fiber 도입의 가장 큰 난제가 TLS 의 관리를 FLS 로 redirect 시키는 것이 Managed 환경에서는 어느 정도 가능하지만, .NET 이 의존하고 있는 수많은 Win32 API 차원에서도 해결되어야 할 문제인지라.)

(2)번의 경우 역시, "현실적으로", "프로그램을 망가뜨릴 각오"를 하고 CLR 호스팅을 커스터마이징하지 않는 이상은 "두개 이상의 운영체제 쓰레드가 하나의 Managed Thread와 매핑"되는 경우는 없다고 봅니다. 이 문제 또한, .NET 이 의존하고 있는 Win32 차원에서의 해결이 같아 되어야 할 뿐더러, fiber 의 경우처럼 FLS 가 시스템 차원에서 제공되는 것도 아니기 때문에 (1)번의 경우보다 더욱 어려운 문제일 거라 봅니다.

즉, 저는 여전히 해당 쓰레드 문서 관련해서 (1), (2) 번과 같은 식으로 구현된 CLR 호스팅은 없다고 보고 있기 때문에 "GetCurrentThreadId" 와 "ManagedThreadId" 값이 여전히 100% 신뢰할 수 있다는 의견입니다.

만약, 향후에 나올 OS 쓰레드에 의존하지 않는 CLR 이 있다면, 아마도 TLS 와 관련된 많은 API 들에 대해 사용할 수 없도록 하는 장치가 따라줘야 하지 않을까 예상해봅니다.

말씀하신 쓰레드 관련해서 OS 쓰레드에 독립적으로 .NET 관리 쓰레드가 동작된다는 것은 말 그대로 "이상"일 뿐 가능한 것은 아니며, 실제로 현재 그것이 구현된 예는 전혀 없습니다. 이것은 마치, ".NET Framework" 이 OS 에 상관없이 올라갈 수 있다는 "이상"이 결국 Windows OS 이외에 어떠한 OS 에도 제대로 포팅되지 못했다는 것과 같을 것입니다. 실제로, Fiber 구현에 대해서는 "Thankfully, re-enabling it (for those playing w/ SSCLI) would be a somewhat trivial exercise." 와 같은 말에 언급된 것처럼 "SSCLI" 정도일 뿐이고요. 아마도 Microsoft 는 SQLCLR 을 시작하면서 OS 쓰레드 독립적인 것을 제거하는 것을 기반으로 다른 OS 에 포팅하는 것에 대한 가능성을 점검해 봤을 지도 모르겠습니다.
kevin25
2007-05-26 07시09분
[Loner] 네... 사실상 GetCurrentThreadId와 ManagedThreadId 가 1:1 매핑이 안되는 호스트를
"개발"하기는 쉽지 않을 겁니다. 하지만 내가 개발하기 어렵다고 해서
그것을 100% 제외시키는 것 역시 옳지 않다는 생각이지요.
알고서 그것을 쓰는 것과 모르고 쓰는 것은 전혀 다른 얘기가 될 수 있으니까요.
신뢰할 수 없기 때문에 쓰지 말라는 '취지'로 Obsolete로 마킹된 것은 다 이유가
있기 때문아닐까요?
일반적인 콘솔이나 윈폼 어플리케이션과 같은 상황이라면 안전하게
GetCurrentThreadId를 사용할 수 있겠지만 그렇지 않은 상황이 존재할 수 있기
때문이며 이러한 상황은 Fiber를 고려 하지 않더라도 1.1 시절부터 있었다는 것이
제 생각입니다.
그리고... 성태씨 생각같이 "사실상 불가능" 하다라는 결론보다는
그런 상황까지 유발하는 호스트가 거의 없다라는 쪽의 결론이 보다 낫지 않나
싶습니다.
[손님]
2007-05-26 08시20분
넵. 그렇다고 제 글이 ManagedThreadId 를 쓰지 말라는 글은 아니었습니다. ^^
첫번째 글의 마지막 부분을 보시면 저 역시 ^^ Loner 님과 같은 결론을 냈었지요. 단지,,, 글의 취지가 ManagedThreadId 를 사용하는 것에 대한 의미가 있을 수 있는 환경에 대한 배경을 설명한 것 뿐입니다. ^^

정리해 보면.
어쨌든, 재미있는 차이점이라면, Loner 님은 1.1 시절부터 그런 CLR 호스팅 버전이 있었을 것이라는 의견이시고.
저는 1.x 는 물론이고 2.0 인 현재까지도 그런 CLR 호스팅 버전은 없을 것이라는 의견이고요.

공통된 결론이라면. 어쨌든 ManagedThreadId 를 쓰자는 것이고. ^^
kevin25
2007-09-02 10시40분
오늘 문득. 이 글을 읽고 ... Katmai 에서 구현된 SqlCLR 에는 Fiber 모드가 구현되어 있을까 ... 하는 궁금함이 생겨서 검색을 해봤습니다.

Integrated CLR and fiber-mode
; http://forums.microsoft.com/TechNet/ShowPost.aspx?PostID=1914052&SiteID=17
kevin25

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12382정성태10/26/202018오류 유형: 671. dotnet build - The local source '...' doesn't exist
12381정성태10/26/202076VC++: 137. C++ stl map의 사용자 정의 타입을 key로 사용하는 방법파일 다운로드1
12380정성태10/26/202032오류 유형: 670. Visual Studio - Squash_FailureCommitsReset
12379정성태10/26/2020136.NET Framework: 955. .NET 메서드의 Signature 바이트 코드 분석파일 다운로드2
12378정성태10/20/2020147.NET Framework: 954. C# - x86/x64 환경에 따라 달라지는 P/Invoke 함수의 export 이름파일 다운로드1
12377정성태10/15/2020149디버깅 기술: 172. windbg - 파일 열기 시점에 bp를 걸어 파일명 알아내는 방법(Managed/Unmanaged)
12376정성태10/15/202059오류 유형: 669. windbg - sos의 name2ee 명령어 실행 시 "Failed to request module list." 오류
12375정성태10/15/2020182Windows: 177. 윈도우 탐색기에서 띄우는 cmd.exe 창의 디렉터리 구분 문자가 'Yen(¥)' 기호로 나오는 경우 [1]
12374정성태10/14/2020172.NET Framework: 953. C# 9.0 - (6) Function pointers파일 다운로드2
12373정성태10/14/202077.NET Framework: 952. OpCodes.Box와 관련해 IL 형식으로 직접 코딩 시 유의할 점
12372정성태10/14/2020160.NET Framework: 951. C# 9.0 - (5) Attributes on local functions파일 다운로드1
12371정성태10/13/202059개발 환경 구성: 519. Visual Studio의 Ctrl+Shift+U (Edit.MakeUppercase) 단축키가 동작하지 않는 경우
12370정성태10/13/202059Linux: 33. Linux - nmcli를 이용한 고정 IP 설정
12369정성태10/21/2020864Windows: 176. Raymond Chen이 한글날에 밝히는 윈도우의 한글 자모 분리 현상 [1]
12368정성태10/12/202053오류 유형: 668. VSIX 확장 빌드 - The "GetDeploymentPathFromVsixManifest" task failed unexpectedly.
12367정성태10/12/202055오류 유형: 667. Ubuntu - Temporary failure resolving 'kr.archive.ubuntu.com'
12366정성태10/13/2020147.NET Framework: 950. C# 9.0 - (4) Native ints파일 다운로드1
12365정성태10/12/2020148.NET Framework: 949. C# 9.0 - (3) Lambda discard parameters파일 다운로드1
12364정성태10/11/2020192.NET Framework: 948. C# 9.0 - (2) Skip locals init파일 다운로드1
12363정성태10/27/2020206.NET Framework: 947. C# 9.0 - (1) Target-typed new파일 다운로드1
12362정성태10/11/2020167VS.NET IDE: 151. Visual Studio 2019에 .NET 5 rc/preview 적용하는 방법
12361정성태10/19/2020257.NET Framework: 946. C# 9.0을 위한 개발 환경 구성
12360정성태10/8/202074오류 유형: 666. The type or namespace name '...' does not exist in the namespace 'Microsoft.VisualStudio.TestTools' (are you missing an assembly reference?)
12359정성태10/7/202069오류 유형: 665. Windows - 재부팅 후 iSCSI 연결이 끊기는 문제
12358정성태10/7/202059오류 유형: 664. Web Deploy 설치 시 "A newer version of Microsoft Web Deploy 3.6 was found on this machine." 오류
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...