Special threads in CLR

Question: How many threads does a typical managed process have when it just starts to run?  

Answer: regardless how many threads the user creates, there are at least 3 threads for a common managed process after CLR starts up: a main thread which starts CLR and run user’s Main method, CLR debugger helper thread which provides debugging service for interop debuggers like Visual Studio, and the finalizer thread which runs finalizers for unreachable objects. Depends on what the program does, CLR might create more threads to perform special tasks.   

Sometimes it is important to know what “special” threads would be created in CLR so we could understand better the implicit impact of our managed programs. Here is a list of most common special threads: 

1. Finalizer thread. The thread is to run finalizers for “dead” objects. This thread is created when GC heap is initialized during EE start up. In Rotor, the thread proc for the thread is GCHeap::FinalizerThreadStart in vm\gcee.cpp. Because GC is undeterministic and finalizers are executed in a separate thread, you can’t predict when exactly an object will be finalized. Because there is only one thread to run all finalizers, if one finalizer is blocked, no other finalizers could run. So it is discouraged to take any lock in finalizer. Also see Maoni Stephens’s blog for details about finalizer thread.

2. Debugger helper thread. As its name suggests, this thread helps debuggers (mixed mode and managed debugger, but not pure unmanaged debug like windbg) to get information of the managed process and to execute certain debugging operations. The thread is created when EE initializes debugger during start up. In Rotor, the thread proc for this thread is DebuggerRCThread::ThreadProcStatic (debug\ee\Rcthread.cpp). Also see Mike Stall’s blog about impact of this helper thread。

3. Concurrent GC thread (doesn’t exist in Rotor). As explained in Maoni and Chris Lyon’s blog, concurrent GC is a special GC mode which allows garbage to be collected while managed threads are running simultaneously. To achieve this goal, CLR creates a thread to perform GC concurrently with user threads. The thread is only created when CLR decides to do a concurrent GC (even when concurrent GC mode is on, not every GC is concurrent, read Maoni’s blog for details) and will be recycled when there are no concurrent GC work to do.

4. Server GC threads (doesn’t exist in Rotor). Maoni and Chris also explained Server GC mode where on multi-process machine CLR creates one GC heap for each CPU and one thread to do GC for each heap. When Server GC mode is enabled, server GC threads will be created at EE start up time when GC heaps are initialized.

5. App Domain unload helper thread. In CLR V1.X, when a thread requests to unload an App Domain and the thread is in that App Domain itself, it needs to create a worker thread to do the unloading work. The worker thread will be dead once the target AD is unloaded. In Rotor, the thread starts with UnloadThreadWorker.ThreadStart (bcl\system\Appdomain.cs). In Whidbey, all AD unload work is performed in a special thread regardless whether the requesting thread is in the unloading domain. The helper thread is created when first non-default App Domain is created (default domain is never unloaded) and will stay alive since then. Also see Chris Brumme’s blog about details of AD unload.

6. Threadpool threads. Depends on how a program use CLR threadpool, CLR might create threads of a varieties of types. There is only one thread for some thread type. For other types, number of threads is related to number of CPUs, the work load, and some user configurable settings. The thread types including wait threads (threads to perform asynchronized wait, could be more than one); worker threads (threads to execute user work item, could be more than one); Completion port threads (threads wait for completion port IO in Windows, could be more than one, doesn’t exist in Rotor); Gate thread (thread help to monitor status of completion port threads and worker threads, only one); Timer thread (thread manages timer queue, only one).

Comments (15)

  1. Special threads in CLR



  2. Adam whips up some Monad script blocks to help out my poor Monad script.

    Andrew D. Birrell has…

  3. Daniel Moth says:

    Nice info! In case anybody is interested, the .net compact framework v1.0 also has 3 special threads (in addition to the UI thread):

    1. Finalizer thread

    2. Controlling various timers

    3. Tracking changes to tcp interfaces

    My only concern with all these special threads (on both platforms) is that you cannot change their thread priority. So if you radically changed the priority of your own threads, the impact on your app is not entirely under your control.

  4. jmstall says:

    Note that the CLR debugger thread is used for managed-only debugging too, and not just mixed-mode. In whidbey, it always exists, even if when you’re not debugging, so that it’s available to service attach requests.

  5. I got email asking me to explain !Threads output in details. I think this is a good question and a good…

  6. That’s a fair question. Part of the answer is we don’t believe people could use it properly.  The…

  7. Jeff Stong says:

    I occasionaly visit Mike Stall’s .NET Debugging Blog because it’s interesting to peer beneath the hood…

  8. There are multiple special threads which the CLR maintains and executes. Yun Jin talks about them in…

  9. Last update: June 13 , 2007 Document version 0.6 Preface If you have something to add, or want to take

  10. INFO: · "COM Descriptor Directory" part of the PE is responsible whether executable file is