Когда я экспериментировал с тестированием границ параллельной системы, я сам столкнулся с этой проблемой. Комментарий oleksii точен (1k потоков ~ = 1 ГБ выделенной памяти). Важно отметить, что эта память является зарезервированным виртуальным адресным пространством, а НЕ объемом фактически используемой памяти. Исключения из нехватки памяти возникают, когда система не может зафиксировать непрерывный фрагмент виртуального адресного пространства, достаточно большого для удовлетворения вашего запроса (вставьте здесь риторику «фрагментация памяти»). Если вы просматриваете процесс в диспетчере задач Windows примерно в то время, когда он умирает, вы можете увидеть всего лишь 80-120 МБ «использованной» памяти. Чтобы узнать, сколько виртуального адресного пространства зарезервировано, откройте столбец «Память - размер фиксации» в диспетчере задач.
Для краткости я смог преодолеть ограничение потока ~ 1k, переключив конфигурацию сборки с x86 на 64 бит. Это увеличивает объем доступного виртуального адресного пространства с (примерно) 2 ГБ до 6 ТБ + (в зависимости от версии вашей ОС), и мое исключение OutOfMemoryException исчезло.
Вот простая программа, которую я создал, которая иллюстрирует этот артефакт, обязательно запустите его как x86 и наблюдайте, как он умирает где-то между потоками 1k и 1.5k - затем переключитесь на 64-битную версию и она должна завершиться без сбоев.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace TaskToy
{
class Program
{
static void Main( string[] args )
{
List<Task> lTasks = new List<Task>();
int lWidth = 0;
for ( int i = 0; i < 5000; i ++ )
{
lTasks.Add( new Task( (o) => {
Console.WriteLine( "B " + Interlocked.Increment( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
Thread.Sleep( 60000 );
Console.WriteLine( "E " + Interlocked.Decrement( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
}, null, TaskCreationOptions.LongRunning ) );
}
Parallel.For( 0, lTasks.Count, ( i ) =>
{
lTasks[i].Start();
} );
Task.WaitAll( lTasks.ToArray() );
Console.WriteLine( "DONE - press any key..." );
Console.ReadKey( true );
}
}
}
P.S. Переменная 'lWidth' указывает текущий уровень параллелизма, то есть сколько задач фактически выполняется одновременно.
В целом это был забавный академический эксперимент, но, вероятно, пройдет несколько лет, прежде чем запуск тысяч потоков принесет полезную отдачу. Вероятно, целесообразно ограничить количество потоков, которые вы раскручиваете, до чего-то более практичного - вероятно, на порядок меньше, чем «тысячи».