Understand the .Net CLR thread pool

Knowing how thread injection works in .Net is key to allowing your ASP.Net application to make the best use of system resources

Understand the .NET CLR thread pool
Thinkstock

In the .Net Framework, the CLR is responsible for meting out resources to running applications. In particular, the CLR thread pool determines when threads are to be added or taken away. Understanding how this works will help you determine how to configure your ASP.Net application for optimal performance.

The CLR thread pool contains two kinds of threads—the worker threads and the I/O completion port or IOCP threads. That means your ASP.Net worker process actually contains two thread pools: the worker thread pool and the IOCP thread pool. Naturally, these pools have different purposes.

When you use methods like Task.Run, TaskFactory.StartNew, and ThreadPool.QueueUserWorkItem, the runtime takes advantage of worker threads for processing. When you make asynchronous I/O calls in your application, or your application accesses the file system, databases, web services, etc., then the runtime uses IOCP threads. Note too that each application domain has its own thread pool.

Let’s take a closer look at how these threads are created and removed in the .Net Framework. 

Thread injection strategies

The .Net thread pool starts injecting new threads whenever the number of busy threads becomes equal to the number of configured minimum threads in the thread pool. The default value of the minimum setting, which is the minimum number of both worker and IOCP threads, is determined by the number of processors in your system. Hence, if your system has four cores, you would have four worker threads and four IOCP threads by default. 

The .Net thread pool then injects additional worker threads on demand if existing threads are utilized and there is still work to be done. By the same token, if the demand for resources falls, the thread pool will begin taking threads away. 

Executing the following code snippet would display the number of logical processors in your system and the minimum number of worker and IOCP threads available.

static void Main(string[] args)
{
    int minimumWorkerThreadCount, minimumIOCThreadCount;
      int logicalProcessorCount = System.Environment.ProcessorCount;
      ThreadPool.GetMinThreads(out minimumWorkerThreadCount, out minimumIOCThreadCount);
      Console.WriteLine(“No. of processors: “ + logicalProcessorCount);
     Console.WriteLine(“Minimum no. of Worker threads: “ + minimumWorkerThreadCount);
      Console.WriteLine(“Minimum no. of IOCP threads: “ + minimumIOCThreadCount);
      Console.Read();
}

The .Net thread pool manages threads using its built-in heuristics. The strategies adopted include starvation avoidance and a hill-climbing algorithm. In the former case, the .Net thread pool continues to add worker threads if there is no visible progress on the queued items. In the latter case, the .Net thread pool tries to maximize the throughput using as few threads as possible.

The .Net thread pool injects or removes threads at intervals of 500 milliseconds or as a thread becomes free, whichever comes first. Now, based on the feedback available to the runtime, the .Net thread pool either removes threads or adds threads to maximize the throughput. If adding a thread does not increase throughput, it takes a thread away. This is the CLR’s hill-climbing technique in action.

Now suppose you are running your ASP.Net application on IIS and your web server has a total of four CPUs. Assume that at any given point in time, there are 24 requests to be processed. By default the runtime would create four threads, which would be available to service the first four requests. Because no additional threads will be added until 500 milliseconds have elapsed, the other 20 requests will have to wait in the queue. After 500 milliseconds have passed, a new thread is created.

As you can see, it will take many 500ms intervals to catch up with the workload. This is a good reason for using asynchronous programming. With async programming, threads aren’t blocked while requests are being handled, so the four threads would be freed up almost immediately. 

Recommended thread settings

Given the way the .Net thread pool works and what we have discussed thus far, it is strongly recommended that you change the minimum configuration value—the default value—for both worker and IOCP threads. To do this in ASP.Net, you should change the minWorkerThreads and minIoThreads configuration settings under the <processModel> configuration element in the machine.config file in your system.

<configuration>
 <system.web>
    <processModel minWorkerThreads=”provide your desired value here”
           minIoThreads=”provide your desired value here” />
 </system.web>
</configuration>

You can set the minimum configuration values for both worker and IOCP threads to any value between one and 50. A good approach is to take a user mode process dump of the IIS worker process (W3wp.exe) and then use the !threadpool command to report the total number of worker threads. Once you know this value, simply divide it by the number of processor cores on your system to determine the minimum worker and IOCP thread settings. For example, if the total count of worker threads is 100 and you have four processors in your system, you can set the minimum values for both worker and IOCP threads to 25.

To change the default minimum thread settings outside of ASP.Net, you can use the ThreadPool.SetMinThreads() method.

With the goal of better thread management and improved performance, the CLR thread pool has been improved with each version of the CLR. As an example, with .Net Framework 4, the CLR gained thread stealing algorithms and support for concurrency and parallelism. With each new version of the CLR, the .Net thread pool is getting smarter about optimizing the throughput by creating and destroying threads as needed. In the meantime, you’ll want to experiment with different minimum thread settings to get the best performance from your .Net application.