Max Number of Threads Per Windows Process

Developers usually thinks that Windows has a default value for the max number of threads a process can hold. while in fact the limitation is due to the amount of address space each thread have can have.

I was working lately on some mass facility simulation applications to test some complex RFID based system. I needed to have a different thread for each person in the facility to simulate parallel and different movement of people at the same time .. and that’s when the fun started Smile.with the simulation running more than 3000 individuals at the same time, it wasn’t too log before I got the OutOfMemory Unfriendly Exception.

When you create new thread, it has some memory in the kernel mode, some memory in the user mode, plus its stack, the limiting factor is usually the stack size. The secret behind that is the default stack size is 1MB and the user-mode address space assigned to the windows process under 32 bit Windows OS is about 2 GB. that allow around 2000 thread per process (2000 * 1MB = 2GB).

Hit The Limit!

imageRunning the following code on my virtual machine with the specs in the image runs around ~ 1400 Threads, the available memory on the machine also takes place in deciding the Max number of threads can be allocated to your process.

Now running the same application multiple times on different machines with different configs will give a close or similar results.

using System;
using System.Collections.Generic;
using System.Threading;

namespace MaxNumberOfThreadsDemo
{
    public class Program
    {
        private static List<Thread> _threads = new List<Thread>();

        public static void Main(string[] args)
        {
            Console.WriteLine("Creating Threads ...");
            try
            {
                CreateThreadsWithDefaultStackSize();
                //CreateThreadsWithSmallerStackSize();
                //CreateThreadsWithSmallerThanMinStackSize();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();
            Console.WriteLine("Cleanning up Threads ...");
            Cleanup();

            Console.WriteLine("Done");
        }


        public static void CreateThreadsWithDefaultStackSize()
        {
            for(int i=0; i< 10000; i++)
            {
                Thread t = new Thread(DoWork);
                _threads.Add(t);
                t.Name = "Thread_" + i;
                t.Start();
                Console.WriteLine(string.Format("{0} started!",t.Name));
            }
            
        }
        private static  void Cleanup()
        {
            foreach (var thread in _threads)
                thread.Abort();
            _threads.Clear();
            _threads = null;
        }

        private static void DoWork()
        {
            Thread.Sleep(Int32.MaxValue-1);
        }
    }
}

image

How Can you extend the Limit?

Well, There are several ways to get around that limit, one way is by setting IMAGE_FILE_LARGE_ADDRESS_AWARE variable that will extend the user mode address space assigned to each process to be 3GB on 32x windows or 4GB on 64x.

Another way is by reducing the stack size of the created thread which will in return increases the maximum number of threads you can create. You can do that in C# by using the overload constructor when creating a new thread and provide maxStackSize.

Thread t = new Thread(DoWork,256);

Beginning with the .NET Framework version 4, only fully trusted code can set maxStackSize to a value that is greater than the default stack size (1 megabyte). If a larger value is specified for maxStackSize when code is running with partial trust, maxStackSize is ignored and the default stack size is used. No exception is thrown. Code at any trust level can set maxStackSize to a value that is less than the default stack size.

Running the following same code after setting the maxStackSize of the created threads to 256KB will increase the number of max threads created per process.

image

Now If maxStackSize is less than the minimum stack size, the minimum stack size is used. If maxStackSize is not a multiple of the page size, it is rounded to the next larger multiple of the page size. For example, if you are using the .NET Framework version 2.0 on Microsoft Windows Vista, 256KB (262144 bytes) is the minimum stack size, and the page size is 64KB (65536 bytes). according to MSDN.

In closing, resources aren’t free, and working with multiple threads can be really challenging in terms of reliability, stability, and extensibility of your application. but definitely hitting the max number of threads is something you want to think about carefully and consider maybe only in lab or simulation scenarios.

More Information

Hope this Helps,

Ahmed

CLR 4.0: Type Embedding

Note: This Post transfered from my OLD Blog and was originally posted in 2008.

 

As new enhancement in CLR version 4.0 (will be released in 2010) is the concept of Type Embedding. The actual motivation for this new concept was the miserable story of deploying application that uses Primary Interop Assemblies (PIA).

In previous versions of CLR you’ve needed to deploy your managed assembly plus the PIA to the client machine, in some scenarios like developing against the MS Office’s PIAs, you need to deploy the whole Office PIA Redist which is about 6.3 MB to make your little application works!!

and the problem getting worth if you try to target a machine that have different version of MS Office’s PIAs

This was a very bad experience if you have been through it before.

image

So the Scenario was trying to develop against PIAs leads to:

  • Complex deployment scenarios
  • Targeting multiple hosting environment.
  • Tight type coupling.

MS started a new project code-name NOPIA as part of the next version of CLR 4, its target was to eliminate runtime dependency on Interop Assemblies at compile time easily, So you can do that with just flipping a switch in the visual studio assembly reference properties window or just by compiling your code with /link switch.

What’s NOPIA

Using this new capability you are actually telling the CLR to embed all the necessary information to call the com object embedded into the managed assembly itself, so you don’t need the PIA assembly to be deployed with your application anymore.

The embedded information represented as “Local Types” which is a partial copies of the types exist in the PIA.

As an example we will develop this sample Hello Buddy console Application simply takes a name and interact with Word PIA ( Microsoft.Office.Word.dll ) from Office PIA Redist, to create new word document and Write “Hello name” statement.

image

I’m using Visual Studio 2010, .Net 4.0, and CLR v4.0 CTP to develop this application, you can download this CTP here.

  • Create new C# console application.
  • Add reference for Microsoft.Office.Interop.Word.dll
  • Add this class to your applicaion.
 1: using System;
 2: using Word = Microsoft.Office.Interop.Word;
 3:
 4: namespace HelloBuddy
 5: {
 6:     public class Program
 7:     {
 8:         static void Main(string[] args)
 9:         {
 10:             SayHi("Ahmed");
 11:             Console.ReadLine();
 12:         }
 13:
 14:         public static void SayHi(string name)
 15:         {
 16:             Word.Application wordApp = new Word.Application();
 17:
 18:             wordApp.Visible = true;
 19:             wordApp.Activate();
 20:
 21:             object falseValue = false;
 22:             object trueValue = true;
 23:             object missing = Type.Missing;
 24:
 25:             Word.Document doc = wordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
 26:
 27:             object start1 = 0;
 28:             object end1 = 0;
 29:
 30:             Word.Range rng = doc.Range(ref start1, ref missing);
 31:             rng.Font.Name = "Tahoma";
 32:             rng.InsertAfter("Hello " + name);
 33:
 34:         }
 35:
 36:     }
 37: }
  • Compile and run
  • You should be apple to see MS Word window open with “Hello Ahmed” statement.Check the compiled assemblies in your bin directory, you’ll find:
    1. HelloBuddy.exe
    2. Microsoft.Office.Interop.Word.dll.

    Attach “HelloBudy.exe” to your VS Debugger, and check the loaded modules, you will find the Office PIA assembly is loaded, and this is the one we are trying to get rid of

image

loadedModules1NOPIA Mode

Now let’s switch to NOPIA Mode, and embed those PIA used types into our console assembly, to do that:

  • Select the PIA assembly reference and choose properties
  • In Properties window, change property “Embed Interop Types” to True
  • Recompile your application.

If you check the Bin directory you will find only the application assembly ”HelloBuddy.exe ”, there is no PIA assembly. The Only difference is application assembly becoming little bit bigger because it now embed the partial type info from the original PIA, but still smaller than deploying the whole PIA file.

Attach “HelloBudy.exe” to your VS Debugger, and check the loaded modules, you will find only your application assembly, no more PIA.

Behind the Scene

If you are interested to know what actually CLR do behind the scenes, open your application assembly with Reflector and check the types inside it, you will find that CLR has injected Microsoft.Office.interop.Word namespace into your application’s assembly.

embed
In this namespace you can find only the set of types “local types” that you have used from the PIA into your application. Types like  Application, Document, Range, Font.
In fact the CLR rips only the types necessary to complete the calls you have made from your application, More than that if you check the emitted code for those types, for instance the Application type. CLR extracted only the functions you have called and replaced all the other functions and Type members with the magic _VblGap calls. Those _VtblGap pseudo methods are emitted in place of unused methods to maintain vtable compatibility.
embedIL0

NOPIA Limitations

Ofcourse there is nothing without limitations. NOPIA has some limitation of what you can embed into your assemblies:

  1. Can’t Embed IL (no classes or static methods)
  2. Only metadata is locally embedded (interfaces, delegates, enums, structs )
  3. Only types from Interop Assemblies can be embedded. Compilers check for these attributesa. [assembly:Guid(…)]b. [assembly:ImportedFromTypeLib(…)]

As you can see how the MS Future CLR v4.0 provides an easy way to develop application against COM interop assemblies, along with very friendly, powerful deployment scenario.

Related stuff

Hope this helps,

Ahmed