. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.
суббота, 4 сентября 2010 г.
Имеет ли Windows ограничение в 2’000 потоков на процесс?
Я часто вижу, как люди спрашивают, почему они не могут создать больше (примерно) 2’000 потоков в процессе. Причина не в каком-то специальном ограничении в Windows. Просто программист забыл принять во внимание размер адресного пространства процесса, используемого каждым потоком.
Поток состоит из памяти в ядре (стеки ядра и управление объектом), памяти в пользовательском режиме (TEB (Thread Environment Block), TLS (Thread Local Storage) и подобные вещи), плюс его стек (или стеки, если вы работаете на процессорах Itanium).
Обычно ограничивающим фактором является размер стека:
Эта программа обычно печатает значение около 2’000 (прим.пер.: если вы хотите повторить этот эксперимент, то вам лучше бы запускать программу вне отладчика; также, запуск программы на x64 даст вам совсем другие числа — из-за вмешательства слоя эмуляции 32-х разрядных процессов).
Для чего нужны ядра и потоки в процессоре?
Почему она сдаётся на числе 2’000?
Потому что размер резервируемого адресного пространства для стека потока по-умолчанию равен 1 Мб, а 2’000 стеков по 1 Мб равняются примерно 2 Гб — именно столько доступно коду пользовательского режима.
Вы можете попробовать втиснуть больше потоков в ваш процесс уменьшением начального размера стека — вы можете сделать это либо указанием размера в заголовке PE-файле, либо вручную указав размер стека в функции CreateThread, как указано в MSDN:
С этим изменением я смог создать около 13’000 потоков (прим.пер.: у меня получилось 30’469 — это и есть ожидаемое число: около 30’000; у Реймонда же сработало другое ограничение, а не стек; см. также ссылку на статью Марка Руссиновича в конце поста). Хотя это определённо лучше, чем 2’000, это меньше наивного ожидания 500’000 потоков (примерно столько влезет кусков по 4 Кб в 2 Гб).
Потому что вы забыли о других накладных расходах. Гранулярность выделения адресного пространства — 64 Кб, так что каждый стек занимает 64 Кб адресного пространства, даже хотя он использует всего 4 Кб (прим.пер.: вот откуда число в примерно 30’000). Плюс, конечно же, у вас нет полностью свободных 2 Гб. Часть уже занята под системные DLL и прочие штуки.
Но настоящий вопрос, который встаёт, когда кто-то спрашивает: «Какое максимальное количество потоков я могу создать» — это: «Почему вы создаёте так много потоков, что это становится проблемой?»
Модель «один клиент — один поток» хорошо известна тем, что не масштабируется выше примерно дюжины клиентов. Если вы собираетесь обслуживать большее число клиентов одновременно, то вам лучше бы использовать другую модель, при которой вместо выделения потока клиенту вы просто создаёте объект (когда-нибудь я буду размышлять о двойственности между потоками и объектами). Windows предоставляет вам порты завершения ввода-вывода и пулы потоков, чтобы помочь вам перейти от модели клиент-поток к модели клиент-задача.
Как посмотреть количество ЯДЕР и ПОТОКОВ центрального процессора в Windows 10 через диспетчер задач?
Заметьте, что волокна (fiber) не очень-то тут помогут, потому что у волокна тоже есть стек, а почти всё время ограничивающим фактором является именно стек.
Примечания переводчика:
1. Рекомендую почитать ещё статью Марка Руссиновича.
2. Вообще по теме серверных приложений и обслуживания клиентов я рекомендую отличную книгу от небезызвестного Джеффри Рихтера.
3. Надеюсь, я не надоел вам своими примечаниями 🙂 Что-то их получилось выше крыши в этом посте. Я обещаю стараться сводить их к минимуму.
Источник: www.transl-gunsmoker.ru
Пул управляемых потоков
Класс System.Threading.ThreadPool обеспечивает приложение пулом рабочих потоков, управляемых системой, позволяя пользователю сосредоточиться на выполнении задач приложения, а не на управлении потоками. Если имеются небольшие задачи, которые требуют фоновой обработки, пул управляемых потоков — это самый простой способ воспользоваться преимуществами нескольких потоков. В Framework 4 и более поздних версиях использовать пул потоков стало значительно проще, так как вы можете создавать объекты Task и Task , которые выполняют в потоках пула асинхронные задачи.
Платформа .NET использует потоки из пула в различных целях, в том числе для операций библиотеки параллельных задач (TPL), асинхронного ввода-вывода, обратных вызовов таймера, регистрируемых операций ожидания, асинхронного вызова методов с использованием делегатов и для подключения к сокетам System.Net.
Характеристики пула потоков
Потоки из пула являются фоновыми. Для каждого потока используется размер стека по умолчанию, поток запускается с приоритетом по умолчанию и находится в многопотоковом подразделении. Когда поток в пуле завершает свою задачу, он возвращается в очередь потоков в состоянии ожидания. С этого момента его можно использовать вновь. Повторное использование позволяет приложениям избежать дополнительных затрат на создание новых потоков для каждой задачи.
Для каждого процесса существует только один пул потоков.
Исключения в потоках из пула потоков
Необработанные исключения в потоках из пула приводят к завершению процесса. Есть три исключения из этого правила:
- Исключение System.Threading.ThreadAbortException возникает в потоке пула вследствие вызова Thread.Abort.
- Исключение System.AppDomainUnloadedException возникает в потоке пула вследствие выгрузки домена приложения.
- Среда CLR или процесс ведущего приложения прерывает выполнение потока.
Максимальное число потоков в пуле потоков
Число операций, которое можно поставить в очередь в пуле потоков, ограничено только доступной памятью. Однако пул потоков имеет ограничение на число потоков, которое можно активировать в процессе одновременно. Если все потоки в пуле заняты, дополнительные рабочие элементы помещаются в очередь и ожидают их освобождения. Размер по умолчанию пула потоков для процесса зависит от нескольких факторов, таких как размер виртуального адресного пространства. Процесс может вызвать метод ThreadPool.GetMaxThreads для определения количества потоков.
Вы можете управлять максимальным количеством потоков с помощью методов ThreadPool.GetMaxThreads и ThreadPool.SetMaxThreads.
В коде, содержащем среду CLR, этот размер можно задать с помощью метода ICorThreadpool::CorSetMaxThreads .
Минимальные значения пула потоков
Пул потоков предоставляет новые рабочие потоки или потоки завершения ввода-вывода по запросу, пока не будет достигнут заданный минимум для каждой категории. Для получения этих минимальных значений можно использовать метод ThreadPool.GetMinThreads.
Если потребность низкая, фактическое количество потоков из пула потоков может быть ниже минимальных значений.
При достижении минимума пул потоков может создавать дополнительные потоки или ожидать завершения некоторых задач. Пул потоков создает и уничтожает рабочие потоки в целях оптимизации пропускной способности, которая определяется как количество задач, завершаемых за единицу времени. Слишком малое количество потоков может препятствовать оптимальному использованию доступных ресурсов, тогда как слишком большое их количество может усиливать конкуренцию за ресурсы.
Для увеличения минимального количества бездействующих потоков можно использовать метод ThreadPool.SetMinThreads. Однако необоснованное увеличение этих значений может привести к снижению производительности. Если одновременно запускается слишком много задач, все они могут выполняться слишком медленно. В большинстве случаев пул потоков работает наилучшим образом, если он использует собственный алгоритм выделения потоков.
Использование пула потоков
Самым простым способом использования пула потоков является применение библиотеки параллельных задач (TPL). По умолчанию такие типы TPL, как Task и Task , используют потоки из пула для выполнения задач.
Пул потоков также можно использовать путем вызова ThreadPool.QueueUserWorkItem из управляемого кода (или ICorThreadpool::CorQueueUserWorkItem из неуправляемого кода) и передачи делегата System.Threading.WaitCallback, представляющего метод, который выполняет задачу.
Другим способом использования пула потоков является помещение в очередь рабочих элементов, которые имеют отношение к операции ожидания, с помощью метода ThreadPool.RegisterWaitForSingleObject и передача дескриптора System.Threading.WaitHandle, который вызывает метод, представленный делегатом System.Threading.WaitOrTimerCallback, при получении сигнала или истечении времени ожидания. Потоки из пула потоков используются для вызова методов обратного вызова.
Примеры см. по ссылкам на страницы API.
Пропуск проверок безопасности
Пул потоков также предоставляет методы ThreadPool.UnsafeQueueUserWorkItem и ThreadPool.UnsafeRegisterWaitForSingleObject. Используйте эти методы только в том случае, если вы уверены, что стек вызывающего объекта не важен для проверок безопасности, осуществляемых во время выполнения задачи в очереди. ThreadPool.QueueUserWorkItem и ThreadPool.RegisterWaitForSingleObject перехватывают стек вызывающего объекта, который объединяется со стеком потока из пула потоков, когда поток начинает выполнять задачу. Если требуется проверка безопасности, проверяется весь стек. Несмотря на обеспечение безопасности, такая проверка также влияет на производительность.
Когда не следует использовать потоки из пула потоков
Существует ряд сценариев, в которых следует создавать собственные потоки и работать с ними, а не использовать потоки из пула:
- Необходим основной поток.
- Поток должен иметь определенный приоритет.
- Имеются задачи, которые приводят к блокировке потока на длительное время. Для пула потоков определено максимальное количество потоков, поэтому большое число заблокированных потоков в пуле может препятствовать запуску задач.
- Необходимо поместить потоки в однопотоковое подразделение. Все потоки ThreadPool находятся в многопотоковом подразделении.
- Необходимо иметь постоянное удостоверение, сопоставленное с потоком, или назначить поток задаче.
См. также раздел
- System.Threading.ThreadPool
- System.Threading.Tasks.Task
- System.Threading.Tasks.Task
- Библиотека параллельных задач (TPL)
- Практическое руководство. Возвращение значения из задачи
- Объекты и функциональные возможности работы с потоками
- Потоки и работа с потоками
- Asynchronous File I/O
- Таймеры
Источник: learn.microsoft.com
Максимальное колличество одновременных потоков
Физически — не более колличества ядер процессора. Логически — пока не закончится память хандлов (т.е. много). Ваш вопрос сводится к stackoverflow.com/questions/951419/… макс к-ву хандлов (т.к. каждому thread нужно присвоить хандл) тогда ответ — до 10000 (минус штук 300 используется системой). Обьектов Thread может быть создано больше, но «активировать» удастся до 10000.
22 авг 2018 в 12:40
Есть ещё к-во «кластеров» в процессоре для аппаратного-thread — не могу сказать сколько их, и что будет делать ОС если они закончатся.
22 авг 2018 в 12:46
– user303264
22 авг 2018 в 12:48
Вопросов прибавилось, это нечесно, потому что я про системные параметры ядра не знаю. Ответ дан на первую редакцию вопроса, и был дан до правки.
22 авг 2018 в 15:21
– user303264
22 авг 2018 в 15:42
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Есть несколько «понятий» для процесса/потока.
Физически — не более колличества ядер процессора. Т.е. если процессор 4-х ядерный, он не может выполнять более 4-х потоков одновременно. Есть технология Hyper-threading суть которой — оптимизация «переключений» между процессами, т.е. Hyper-Threading 4-8 не означает что 8 выполняются одновременно, а означает что переключение между потоками организовано таким образом, что создаётся впечатление что ядер больше.
Логически (уровень ОС) — пока не закончится память хандлов (т.е. много). Ваш вопрос сводится к макc колличество хандлов (т.к. каждому thread нужно присвоить хандл) тогда ответ — до 10000 (минус штук 300 используется системой). Активные хандлы ОС «преобразует» в таски, а процессор аппаратным решением выбирает какой таск выполнять сейчас на аппаратном уровне. Хандлами могут быть — файлы, pipes, event, mailslot и другие обьекты ОС, если программа активно использует хандлы (например для каждого thread открывается file) то хандлы закончатся в два раза быстрее, т.к. общее число ханлов не должно превышать 10000.
Уровень DOT NET. У с# Thread — это обьект за которым может быть а может не быть реального Thread Handle. Кроме того, хандлы обьеденяются в пул (для повторного использования). Активный Thread всегда подкреплен Thread Handle ОС, но неактивые можно создавать — пока не закончится память Heap. При превышении определённого числа активных Thread — получим ошибку, что не возможно создать хандл.
Апаратный уровнь. TSS или Task Gate Descriptor Для 32 битных процессоров, существует каталог GDT — таблица таблиц на 8192 ячеек. В каждой можно сохранить ссылку на 8192 LDT елементов, один из которых может быть дескриптором процесса Task-и (частью Thread без которой процессор не сможет аппаратно переключать Thread. Т.е.
67 108 864 — это «теоретически» предел аппаратных мест для потоков, но. нужно учесть, что дескрипторы памяти так же нужно разместить в этой таблице, т.е. минус дескрипторы памяти. выйдет от миллиона до 60 миллионов. Но в реальности, есть предел, выше которого процессор будет «ничем другим не занят кроме как переключением процессов», поэтому. столько не используют. Процессор распределает эти таски между ядрами апаратным решением. Что ОС будет делать когда они закончатся — скорее всего у ОС встроен константой предел на колличество тасков, так как при слишком большом их колличестве процессор начнёт терять производительность.
Ccылки по аппаратной части:
- http://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce-246.html
- http://en.wikipedia.org/wiki/Global_Descriptor_Table
Источник: ru.stackoverflow.com