What is exactly happening when I spawn a new thread from .NET? What is exactly happening when I spawn a new thread from .NET? multithreading multithreading

What is exactly happening when I spawn a new thread from .NET?


Win32 and Kernel memory allocated

I'm not exactly sure how the .NET part works, but if the runtime does decide to create a real thread with the OS, it would eventually call the Win32 API CreateThread in kernel32.dll, probably from mscorlib.ni.dll

By default, new threads get 1MB of virtual address for the stack, which is committed as needed. This can be controlled with the maxStackSize parameter. The main thread's stack size comes from a parameter in the executable file itself.

In the process's address space, a TEB (thread environment block) will be allocated (see also). Incidentally, the FS register on x86 points to this for things like thread local storage and structured exception handling (SEH). There are probably other things allocated by Win32 that are not documented.

In creating the Win32 thread, the Win32 server process (csrss.exe) is contacted. You can see that csrss has handles open to all Win32 processes and threads in Process Explorer for some kind of bookkeeping.

DLLs loaded in the process will be notified of the new thread and may allocate their own memory for tracking the thread.

The kernel will create an ETHREAD [layout] (derived from KTHREAD) object from kernel non-paged pool to track the thread's state. There will also be a kernel stack allocated (12k default for x86) which can be paged out (unless the thread is in a kernel mode wait state).

Why so many things need to allocate memory for a thread

Threads are the smallest preemptively scheduled unit that the OS provides and there is a lot of context connected to them. Many different components need to provide separate context for each thread because system services need to be able to deal with multiple threads doing different things all at the same time.

Some services require you to declare new threads to them explicitly but most are expected to work with new threads automatically. Sometimes this means allocating space right when the thread is started. As the thread engages other services, the amount of memory used to track the thread can increase as those services set up their own context for the thread.

How much memory is allocated

It's hard to say how much memory is allocated for a thread since it is spread across several address spaces and heaps. It will vary between Windows versions, installed components and what is loaded into the process currently.

The largest cost is generally accepted to be the 1MB of address space used by default for new threads, but even this limit can allow many hundreds to be used in a single process without running out of space.

If the design is using many more OS threads than the number of CPUs in the system, it should be reviewed. Work queues with a thread pool and lightweight threads with user mode scheduling with fibers or another library's implementation should be able to handle mulithreading without requiring an excessive number of OS threads, rendering the memory cost of the threads to be unimportant.


So this is a really complicated question that does not really have a great answer of "x".

  1. The CLR is not required to map a single CLR thread to a single OS fiber. So... this is hard to answer. I think the current version of .NET (4.0) attempts to use a 1-to-1 relationship between CLR threads and OS fibers when possible on all OSes. Previous versions of .NET (more like <= 1.1) I'm not sure this was the case on all OSes. The scheduler handles most of the these objects and they won't be part of any .NET object graph. This scheduler is part of the CLR and not part of the Thread object. If you dig into the IL, you'll see many internal calls for actual execution.
  2. I assume the question is "Why are those objects needed?" If so, it's because the OS host has to actually have the fiber to execute the code for that thread on it. ThreadPool usage can greatly reduce this cost of creating them each time.
  3. Sorry... depends. A lot of it unmanaged as well, which means the OS host could choose to handle this differently depending on load and system version.

"The logical abstraction of a thread of control is captured by an instance of the System.Threading.Thread object in the class library." http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf

So EMCA standard really doesn't say anything about the topic. But luckily we have...

"Because the CLR thread object is per-fiber, any information hanging off of it is also per-fiber. Thread.ManagedThreadId returns a stable ID that flows around with the CLR thread. It is not dependent on the identity of the physical OS thread, which means using it implies no form of affinity. Different fibers running on the same thread return different IDs. " From Joe Duffy http://www.bluebytesoftware.com/blog/2006/11/10/FibersAndTheCLR.aspx


Look here; there is a mapping between managed (i.e. CLR) primitives and unmanaged (i.e. NT kernel) ones that may answer most of your questions.