为什么一些本机线程在我的代码中有一个没有来源的堆栈跟踪?
Why do some native threads have a stack trace without an origin in my code?
我有一个大量使用任务并行库的 C# .NET 4.5 应用程序,它最终在运行数天后线程匮乏。
当我从 AdPlus 获取 HANG 转储并通过 Visual Studio 查看线程时,我看到 43 个线程在我的代码中没有明显的来源:
ntdll.dll!_NtWaitForSingleObject@12() + 0x15 bytes
ntdll.dll!_NtWaitForSingleObject@12() + 0x15 bytes
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes
为什么这些线程在其堆栈跟踪中没有显示托管来源?
给定进程中的所有线程,甚至TPL线程都有这个启动过程。当您启动一个线程 运行 时,最终 CLR 会调用 OS 来启动一个线程。您正在查看的是线程在启动时执行的函数。如果您暂停任何托管进程,您将看到在堆栈底部有非托管调用。您看不到托管启动过程的原因是每个线程都有自己的堆栈,由 OS 在创建线程时创建。
例如,运行 如下:
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(()=>Thread.Sleep(100000)));
t.Start();
}
Console.ReadKey();
然后使用 WinDbg 进入进程,并查看其中一个休眠线程,给出一个调用堆栈,看起来像这样(所有线程在底部都有相同的两个函数,我只是转储一个对于这个练习。):
0:012> !dumpstack
OS Thread Id: 0x3694 (12)
Current frame: ntdll!ZwDelayExecution+0xa
Child-SP RetAddr Caller, Callee
000000001dc8ea70 000007fefd1c1203 KERNELBASE!SleepEx+0xab, calling ntdll!NtDelayExecution
000000001dc8eae0 000007fefd1c38fb KERNELBASE!SleepEx+0x12d, calling ntdll!RtlActivateActivationContextUnsafeFast
000000001dc8eb10 000007fed860a888 clr!CExecutionEngine::ClrSleepEx+0x29, calling KERNEL32!SleepExStub
000000001dc8eb40 000007fed874d483 clr!Thread::UserSleep+0x7c, calling clr!ClrSleepEx
000000001dc8eba0 000007fed874d597 clr!ThreadNative::Sleep+0xb7, calling clr!Thread::UserSleep
[... removed some frames for clarity ...]
000000001dc8f6f0 000007fed874fcb6 clr!Thread::intermediateThreadProc+0x7d
000000001dc8faf0 000007fed874fc9f clr!Thread::intermediateThreadProc+0x66, calling clr!alloca_probe
000000001dc8fb30 0000000077195a4d KERNEL32!BaseThreadInitThunk+0xd
000000001dc8fb60 00000000773cb831 ntdll!RtlUserThreadStart+0x1d
作为参考,这是 Thread
对象包装我们转储堆栈的线程:
0:012> !do 2a23e08
Name: System.Threading.Thread
MethodTable: 000007fed76522f8
EEClass: 000007fed7038200
Size: 96(0x60) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fed763eca8 4000765 8 ....Contexts.Context 0 instance 0000000000000000 m_Context
000007fed765a958 4000766 10 ....ExecutionContext 0 instance 0000000000000000 m_ExecutionContext
000007fed7650e08 4000767 18 System.String 0 instance 0000000000000000 m_Name
000007fed76534a8 4000768 20 System.Delegate 0 instance 0000000000000000 m_Delegate
000007fed7655390 4000769 28 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentCulture
000007fed7655390 400076a 30 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentUICulture
000007fed76513e8 400076b 38 System.Object 0 instance 0000000000000000 m_ThreadStartArg
000007fed7654a00 400076c 40 System.IntPtr 1 instance 24a5ed0 DONT_USE_InternalThread
000007fed7653980 400076d 48 System.Int32 1 instance 2 m_Priority
000007fed7653980 400076e 4c System.Int32 1 instance 12 m_ManagedThreadId
000007fed7658c48 400076f 50 System.Boolean 1 instance 1 m_ExecutionContextBelongsToOuterScope
000007fed7672e70 4000770 378 ...LocalDataStoreMgr 0 shared static s_LocalDataStoreMgr
>> Domain:Value 00000000005f40b0:NotInit <<
000007fed7672df0 4000771 8 ...alDataStoreHolder 0 shared TLstatic s_LocalDataStore
>> Thread:Value <<
名为 DONT_USE_InternalThread
的 System.IntPtr
brillantly 持有指向 OS 线程的指针。 (我的猜测是它可能是来自 CreateThread
的句柄,但我没有对其进行过多调查。)
(编者注:brillant
是故意这样写的,请不要'fix')
我有一个大量使用任务并行库的 C# .NET 4.5 应用程序,它最终在运行数天后线程匮乏。
当我从 AdPlus 获取 HANG 转储并通过 Visual Studio 查看线程时,我看到 43 个线程在我的代码中没有明显的来源:
ntdll.dll!_NtWaitForSingleObject@12() + 0x15 bytes
ntdll.dll!_NtWaitForSingleObject@12() + 0x15 bytes
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes
为什么这些线程在其堆栈跟踪中没有显示托管来源?
给定进程中的所有线程,甚至TPL线程都有这个启动过程。当您启动一个线程 运行 时,最终 CLR 会调用 OS 来启动一个线程。您正在查看的是线程在启动时执行的函数。如果您暂停任何托管进程,您将看到在堆栈底部有非托管调用。您看不到托管启动过程的原因是每个线程都有自己的堆栈,由 OS 在创建线程时创建。
例如,运行 如下:
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(()=>Thread.Sleep(100000)));
t.Start();
}
Console.ReadKey();
然后使用 WinDbg 进入进程,并查看其中一个休眠线程,给出一个调用堆栈,看起来像这样(所有线程在底部都有相同的两个函数,我只是转储一个对于这个练习。):
0:012> !dumpstack
OS Thread Id: 0x3694 (12)
Current frame: ntdll!ZwDelayExecution+0xa
Child-SP RetAddr Caller, Callee
000000001dc8ea70 000007fefd1c1203 KERNELBASE!SleepEx+0xab, calling ntdll!NtDelayExecution
000000001dc8eae0 000007fefd1c38fb KERNELBASE!SleepEx+0x12d, calling ntdll!RtlActivateActivationContextUnsafeFast
000000001dc8eb10 000007fed860a888 clr!CExecutionEngine::ClrSleepEx+0x29, calling KERNEL32!SleepExStub
000000001dc8eb40 000007fed874d483 clr!Thread::UserSleep+0x7c, calling clr!ClrSleepEx
000000001dc8eba0 000007fed874d597 clr!ThreadNative::Sleep+0xb7, calling clr!Thread::UserSleep
[... removed some frames for clarity ...]
000000001dc8f6f0 000007fed874fcb6 clr!Thread::intermediateThreadProc+0x7d
000000001dc8faf0 000007fed874fc9f clr!Thread::intermediateThreadProc+0x66, calling clr!alloca_probe
000000001dc8fb30 0000000077195a4d KERNEL32!BaseThreadInitThunk+0xd
000000001dc8fb60 00000000773cb831 ntdll!RtlUserThreadStart+0x1d
作为参考,这是 Thread
对象包装我们转储堆栈的线程:
0:012> !do 2a23e08
Name: System.Threading.Thread
MethodTable: 000007fed76522f8
EEClass: 000007fed7038200
Size: 96(0x60) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007fed763eca8 4000765 8 ....Contexts.Context 0 instance 0000000000000000 m_Context
000007fed765a958 4000766 10 ....ExecutionContext 0 instance 0000000000000000 m_ExecutionContext
000007fed7650e08 4000767 18 System.String 0 instance 0000000000000000 m_Name
000007fed76534a8 4000768 20 System.Delegate 0 instance 0000000000000000 m_Delegate
000007fed7655390 4000769 28 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentCulture
000007fed7655390 400076a 30 ...ation.CultureInfo 0 instance 0000000000000000 m_CurrentUICulture
000007fed76513e8 400076b 38 System.Object 0 instance 0000000000000000 m_ThreadStartArg
000007fed7654a00 400076c 40 System.IntPtr 1 instance 24a5ed0 DONT_USE_InternalThread
000007fed7653980 400076d 48 System.Int32 1 instance 2 m_Priority
000007fed7653980 400076e 4c System.Int32 1 instance 12 m_ManagedThreadId
000007fed7658c48 400076f 50 System.Boolean 1 instance 1 m_ExecutionContextBelongsToOuterScope
000007fed7672e70 4000770 378 ...LocalDataStoreMgr 0 shared static s_LocalDataStoreMgr
>> Domain:Value 00000000005f40b0:NotInit <<
000007fed7672df0 4000771 8 ...alDataStoreHolder 0 shared TLstatic s_LocalDataStore
>> Thread:Value <<
名为 DONT_USE_InternalThread
的 System.IntPtr
brillantly 持有指向 OS 线程的指针。 (我的猜测是它可能是来自 CreateThread
的句柄,但我没有对其进行过多调查。)
(编者注:brillant
是故意这样写的,请不要'fix')