О потоках CLR

Часто при проектировании и разработке высокопроизводительных систем, разработчики рассматривают многопоточность как один из способов ускорения работы приложения, а также эффективного распределения нагрузки.

В тоже время многие забывают, что потоки в CLR привязаны к возможностям операционной системы, а следовательно (как мы увидим ниже) имеют достаточно ограниченые возможности. Каждый поток ОС, как известно, требует определенные ресурсы системы, и, в частности, память. Сюда следует также прибавить накладные расходы на управление потоками со стороны ядра ОС.

Вот таким незамысловатым кодом можно проверить “возможности” операционной системы в текущей аппаратной конфигурации.

 class Program
   {
       static void Main(string[] args)
       {
           int i = 0;
           try
           {
               while (true)
               {
                   new Thread(new ThreadStart(() => Thread.Sleep(int.MaxValue))).Start();
                   i++;
               }
           }
           catch (Exception ex)
           {
               Console.WriteLine(i);
               Console.WriteLine(ex.ToString());
           }
       }
   }

После определенного количества времени ожидания получаем вот такое исключение:

ex 

Показания монитора ресурсов подтверждает полученные цифры:

rm

На моей машине с процессором Core 2 Duo, 4ГБ оперативной памяти и ОС Windows Server 2008 R2 x64, в среднем создается около 15-ти тысяч потоков.

Не будем здесь подробно останавливаться о природе таких ограничений (любопытным можно рекомендовать книги Джеффри Рихтера и ресурсы MSDN), отметим лишь один очень важный вывод: использование модели потоков (threads) в операционной системе Windows, а следовательно и в CLR, в виде 1 поток на 1 запрос не является оптимальным решением при проектировании высокопроизводительных систем. Наиболее распространенную альтернативу – пул потоков (Thread Pool) и его реализацию в CLR рассмотрим в ближайшее время.