Сколько потоков в пуле?
октября 12 2009 by admin in Обязательный материалРассмотрим два вопроса. Во-первых, при инициализации службы хотелось бы создавать минимальный набор потоков, чтобы постоянно не создавать и не уничтожать их — ведь на это расходуется дорогое процессорное время. Во-вторых, хотелось бы установить предельное значение числа потоков — на это тоже расходуются системные ресурсы. Даже если большая часть этих ресурсов находится в файле подкачки, экономить никогда не вредно.
Вероятно, вам следует поэкспериментировать с различным числом потоков. Большинство служб (включая службы Интернета Microsoft Internet Information Services) используют эвристические алгоритмы для управления своими пулами потоков. Советую поступить так же. Скажем, для управления пулом потоков можно создать такие переменные:
LONG g_nThreadsMin; // Минимальное количество потоков в пуле LONG g_nThreadsMax; // Максимальное количество потоков в пуле LONG g_nThreadsCrnt; // Текущее количество потоков в пуле LONG g_nThreadsBusy; // Количество занятых потоков в пуле
При инициализации вашего приложения вы можете создать число потоков, равное gjaThreadsMin, каждый из которых будет выполнять одну и ту же функцию для пула потоков. Реализовать эту функцию можно так:
DWORD WINAPI ThreadPoolFunc(PVOID pv) {
// Поток помещается в пул
InterlockedIncrement(&g_nThreadsCrnt);
InterlockedIncrement(&g_nThreadsBusy);
for (BOOL fStaylnPool = TRUE; fStaylnPool;) {
// Поток прекращает выполнение и ожидает, когда ему будет что делать InterlockedDecrement(&m_nThreadsBusy); BOOL fOk = GetQueuedCompletionStatus(...); DWORD dwIOError = GetLastError();
// Потоку есть что делать, так что он занят
int nThreadsBusy = InterlockedIncrement(&m_nThreadsBusy);
// Следует ли нам добавить еще один поток в пул? if (nThreadsBusy == m_nThreadsCrnt) { // Все потоки заняты if (nThreadsBusy < m_nThreadsMax) { // Пул не заполнен
if (GetCPUUsageO < 75) { // Процессор используется менее, чем на 75%
// Добавляем поток в пул CloseHandle(chBEGINTHREADEX(...)); } } >
if (IfOk && (dwIOError == WAIT_TIMEOUT)) { // Таймаут потока if (!ThreadHasIoPending()) {
// Серверу больше нечего делать, и поток можно уничтожить, // поскольку ожидающих запросов ввода-вывода нет fStaylnPool = FALSE; } }
if (fOk || (po != NULL)) {
// Процесс разблокирован, чтобы выполнить определенные действия
if (GetCPUUsageO > 90) { // Процессор используется более, чем на 90Х if (!ThreadHasIoPending()) { // Ожидающих запросов ввода-вывода нет if (g_nThreadsCrnt > g_nThreadsMin)) { // Пул больше минимального
fStaylnPool = FALSE; // Удаляем поток из пула > } > } }
// Поток покинул пул InterlockedDecrement(&g_nThreadsBusy); InterlockedDecrement(&g_nThreadsCurrent); return(O); >
Этот псевдокод показывает, насколько можно быть изобретательным, применяя порт завершения ввода-вывода. Функции GetCPUUsage и ThreadHasIo-Pending не входят в Windows API. Если вам нужна их функциональность, реализуйте их сами. Кроме того, вам следует гарантировать, что пул всегда содержит хоть один поток, иначе клиенты никогда не получат желаемого результата. Вы можете взять мой псевдокод за основу и, изменив его структуру, добиться оптимальной производительности вашей конкретной службы.