Windows для профессионалов

Привязка потоков к процессорам


По умолчанию Windows 2000 использует нежесткую привязку (soft affmity) потоков к процессорам Это означает, что при прочих равных условиях, система пытается выполнять поток на том же процессоре, на котором он работал в последний раз При таком подходе можно повторно использовать данные, все еще хранящиеся в кэше процессора

В повой компьютерной архитектуре NUMA (Non Uniform MemoryAccess) машина состоит из нескольких плат, на каждой из которых находятся четыре процессора и отдельный банк памяти На следующей иллюстрации показана машина с тремя таки ми платами, в сумме содержащими 12 процессоров Отдельный погок может выпол няться на любом из этих процессоров

Система NUMA достигает максимальной производительности, если процессоры используют память на своей плате Если же они обращаются к памяти на другой пла те, производительность резко падает В такой среде желательно, чтобы потоки одно го процесса выполнялись на процессорах 0-3, другого — на процессорах 4-7 и т д Windows 2000 позволяет подстроиться под эту архитектуру, закрепляя отдельные процессы и потоки за конкретными процессорами Иначе говоря, Вы можете конт ролировать, на каких процессорах будут выполняться Ваши потоки Такая привязка называется жесткой (hard affmity)

Количество процессоров система определяет при загрузке, и эта информация ста новится доступной приложениям через функцию GetSystemInfo (о ней — в главе 14). По умолчанию любой поток может выполняться на любом процессоре. Чтобы пото ки отдельного процесса работали лишь на некоем подмножестве процессоров, ис пользуйте функцию SetProcessAffinityMask:

BOOL SetProcessAffinityMask( HANDLE hProcess, DWOHD_PTR dwProcessAffinityMask);

В первом параметре, hProcess, передается описатель процесса. Второй параметр, dwProcessAffinityMask, — это битовая маска, указывающая, на каких процессорах мо гут выполняться потоки данного процесса. Передав, например, значение 0x00000005, мы разрешим процессу использовать только процессоры 0 и 2 (процессоры 1 и 3-31 ему будут недоступны).


Привязка к процессорам наследуется дочерними процессами. Так, если для роди тельского процесса задана битовая маска 0x00000005, у всех потоков его дочерних процессов будет идентичная маска, и они смогут работать лишь на тех же процессо рах. Для привязки целой группы процессов к определенным процессорам используйте объект ядра "задание" (см главу 5).

Ну и, конечно же, есть функция, позволяющая получить информацию о такой привязке;

BOOL GetProcessAffinityMask( HANDLE hProcess, PDWORD_PTR pdwProcessAffiniLyMask, PDWORD_PTR pdwSystemAffinityMask);

Вы передаете ей описатель процесса, а результат возвращается в переменной, на которую указывает pdwProcessAffimtyMask. Кроме того, функция возвращает систем ную маску привязки через переменную, на которую ссылается pdwSystemAffinityMask. Эта маска указывает, какие процессоры в системе могут выполнять потоки. Таким образом, маска привязки процесса всегда является подмножеством системной маски привязки.

WINDOWS 98
В Windows 98, которая использует только один процессор независимо от того, сколько их на самом дслс, GetProcessAffinityMask всегда возвращает в обеих пе ременныхзначенис 1.



До сих пор мы говорили о том, как назначить все потоки процесса определенным процессорам. Но иногда такие ограничения нужно вводить для отдельных потоков. Допустим, в процессе имеется четыре потока, выполняемые на четырехпроцессорной машине. Один из потоков занимается особо важной работой, и Вы, желая повысить вероятность того, что у него всегда будет доступ к вычислительным мощностям, зап рещяете остальным потокам использовать процессор 0.

Задать маски привязки для отдельных потоков позволяет функция:

DWORD_PTR SetThreadAffimtyMask( HANOLE hThread, DWORD_PTR dwThreadAffinityMask);

В параметре hTbread передается описатель потока, a dwThreadAffinityMask опреде ляет процессоры, доступные этому потоку. Параметр dwThreadAffinityMask должен

быть корректным подмножеством маски привяжи процесса, которому принадлежит данный поток Функция возвращает предыдущую маску привязки потока Вот как ог раничить три потока из нашего примера процессорами 1, 2 и 3



// поток 0 выполняется только на процессоре 0
SetThreadAffimtyMask(hThread0 0x00000001);

// потоки 1, 2 3 выполняются на процессорах 1 2 3
SetThreadAffinityMask(hThredd1 0x0000000E);
SetThreadAffimtyMask(hThread2 0x0000000E);
SetThreadAffinityMask(hThread3 0x0000000E);

WINDOWS 98
В Windows 98, которая использует только один процессор независимо от того, сколько их на самом деле, параметр dwThreadAffmityMask всегда должен быть равен 1

При загрузке система тестирует процессоры типа x86 на наличие в них знамени того "жучка" в операциях деления чисел с плавающей точкой (эта ошибка имеется в некоторых Pentium) Она привязывает поток, выполняющий потенциально сбойную операцию деления, к исследуемому процессору и сравнивает результат с тем, что дол жно быть на самом деле Такая последовательность операций выполняется для каж дого процессора в машине

NOTE
В большинстве сред вмешательство в системную привязку потоков нарушает нормальную работу планировщика, не позволяя ему максимально эффектив но распределять вычислительные мощности Рассмотрим один пример

Поток Приоритет Маска привязки Результат
А 4 0x00000001 Работает только на процессоре 0
В 8 0x00000003 Работает на процессоре 0 и 1
С 6 0x00000002 Работает только на процессоре 1
Когда поток А пробуждается, планировщик, видя, что тот жестко привязан к процессору 0, подключает сго именно к этому процессору Далее активизи руется поток В, который может выполняться на процессорах 0 и 1, и плани ровщик выделяет ему процессор 1, так как процессор 0 уже занят Пока все нормально

Но вот пробуждается поток С привязанный к процессору 1 Этот процес сор уже занят потоком В с приоритетом 8, а значит, поток С, приоритет кото рого равен 6, не может его вытеснить Он конечно, мог бы вытеснить поток А (с приоритетом 4) с процессора 0, но у него нет прав на использование этого процессора

Ограничение потока одним процессором не всегда является лучшим решением Всдь может оказаться так, что три потока конкурируют за доступ к процессору 0, тог да как процессоры 1, 2 и 3 простаивают Гораздо лучше сообщить системе, что поток желательно выполнять на определенном процессоре, но, если он занят, его можно переключать на другой процессор



Указать предпочтительный (идеальный) процессор позволяет функция:

DWORD SetThreadIdealProcessor( HANDLE hThread, DWORD dwIdealProcessor);

В параметре hThread передается описатель потока. D отличие от функций, кото рые мы уже рассматривали, параметр dwIdealProcessor содержит не битовую маску, а целое значение в диапазоне 0-31, которое указывает предпочтительный процессор для данного потока. Передав в нем константу MAXIMUM_PROCESSORS (в WinNT.h она определена как 32), Вы сообщите системе, что потоку не требуется предпочтитель ный процессор. Функция возвращает установленный ранее номер предпочтительно го процессора или MAXIMUM_PROCESSORS, если таковой процессор не задан

Привязку к процессорам можно указать в заголовке исполняемого файла. Как ни странно, но подходящего ключа компоновщика на этот случай, похоже, не предус мотрено. Тем не менее Вы можете воспользоваться, например, таким кодом:

// загружаем ЕХЕ-файл в память
PLOADED_IMAGE pLoadedImage = ImageLoad(szExeName, NULL);

// получаем информацию о текущей загрузочной конфигурации ЕХЕ-файла
IMAGE_LOAD_CONFIG_DIRECTORY ilcd;
GetImageConfigInfprmation(pLoadedImage, &ilcd),

// изменяем маску привязки процесса

ilcd.ProcessAffimtyMask = 0x00000003;
// нам нужны процессоры 0 и 1

// сохраняем новую информацию о загрузочной конфигурации
SetImageConfigInformation(pLoadedImage, &ilcd);

// выгружаем ЕХЕ-файл из памяти

ImageUnload(pLoadcdImage);

Детально описывать эти функции я не стану — при необходимости Вы найдете их в документации Platform SDK. Кроме того, Вы можете использовать утилиту Image Cfg.exe, которая позволяет изменять некоторые флаги в заголовке исполняемого мо дуля Подсказку по ее применению Вы получите, запустив ImageCfg.exe без ключей.

Указав при запуске ImageCfg ключ -а, Вы сможете изменить маску привязки для приложения Конечно, все, что делает эта утилита, — вызывает функции, перечислен ные в подсказке по ее применению. Обратите внимание на ключ -u, который сооб щает системе, что исполняемый файл может выполняться исключительно на одно процессорной машине



И, наконец, привязку процесса к процессорам можно изменять с помощью Task Manager в Windows 2000, В многопроцессорных системах в контекстном меню для процесса появляется команда Set Affinity (ее нет на компьютерах с одним процессо ром) Выбрав эту команду, Вы откроете показанное ниже диалоговое окпо и выберете конкретные процессоры для данногопроцесса.



WINDOWS 2000
При запуске Windows, 2000 на машине с процессорами типа x86 можно огра ничить число процессоров, используемых системой. В процессе загрузки сис тема считывает файл Boot.ini, который находится в корневом каталоге загру зочного диска. Вот как он выглядит на моем компьютере с двумя процессорами

[boot loader]
timeout=2
default=multi(0)disk(0)rdisk(0)partition(1)\WINNT

[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINNT= "Windows 2000 Server"
/fastdetecL multi(0)disk(0)rdisk(0)partition(1)\WINNT="Windows 2000 Server"
/fastdetec+ /NurnProcs=1

Этот файл создается при установке Windows 2000, последнюю запись я добавил сам (с помощыо Notepad) Она заставляет систему использовать только один процессор Ключ /NumProcs=l — как раз то зелье, которое и вызывает все эти магические превращения Я пользуюсь им иногда для отладки (Но обычно работаю со всеми своими процессорами)

Заметьте, что ключи перенесены на отдельные строки с отступом лишь для удобства чтения На самом деле ключи и путь от загрузочного раздела жестко го диска должны находиться на одной строке


Содержание раздела