Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6264
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

270 Часть II. Приступаем к работе

HANDLE hThreadPrimary = (HANDLE) pvParam;

SuspendThread(hThreadPrimary);

chMB(

"The Primary thread is suspended.\n"

"It no longer responds to input and produces no output.\n"

"Press OK to resume the primary thread & exit this secondary thread.\n"); ResumeThread(hThreadPrimary);

CloseHandle(hThreadPrimary);

// To avoid deadlock, call EnableWindow after ResumeThread. EnableWindow(

GetDlgItem(FindWindow(NULL, TEXT("Scheduling Lab")), IDC_SUSPEND), TRUE);

return(0);

}

///////////////////////////////////////////////////////////////////////////////

BOOL Dlg_OnInitDialog (HWND hWnd, HWND hWndFocus, LPARAM lParam) {

chSETDLGICONS(hWnd, IDI_SCHEDLAB);

// Initialize process priority classes

HWND hWndCtl = GetDlgItem(hWnd, IDC_PROCESSPRIORITYCLASS);

int n = ComboBox_AddString(hWndCtl, TEXT("High")); ComboBox_SetItemData(hWndCtl, n, HIGH_PRIORITY_CLASS);

// Save our current priority class

DWORD dwpc = GetPriorityClass(GetCurrentProcess());

if (SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS)) {

//This system supports the BELOW_NORMAL_PRIORITY_CLASS class

//Restore our original priority class SetPriorityClass(GetCurrentProcess(), dwpc);

//Add the Above Normal priority class

n = ComboBox_AddString(hWndCtl, TEXT("Above normal")); ComboBox_SetItemData(hWndCtl, n, ABOVE_NORMAL_PRIORITY_CLASS);

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 271

dwpc = 0; // Remember that this system supports below normal

}

int nNormal = n = ComboBox_AddString(hWndCtl, TEXT("Normal")); ComboBox_SetItemData(hWndCtl, n, NORMAL_PRIORITY_CLASS);

if (dwpc == 0) {

//This system supports the BELOW_NORMAL_PRIORITY_CLASS class

//Add the Below Normal priority class

n = ComboBox_AddString(hWndCtl, TEXT("Below normal")); ComboBox_SetItemData(hWndCtl, n, BELOW_NORMAL_PRIORITY_CLASS);

}

n = ComboBox_AddString(hWndCtl, TEXT("Idle")); ComboBox_SetItemData(hWndCtl, n, IDLE_PRIORITY_CLASS);

ComboBox_SetCurSel(hWndCtl, nNormal);

// Initialize thread relative priorities

hWndCtl = GetDlgItem(hWnd, IDC_THREADRELATIVEPRIORITY);

n = ComboBox_AddString(hWndCtl, TEXT("Time critical")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_TIME_CRITICAL);

n = ComboBox_AddString(hWndCtl, TEXT("Highest")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_HIGHEST);

n = ComboBox_AddString(hWndCtl, TEXT("Above normal")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_ABOVE_NORMAL);

nNormal = n = ComboBox_AddString(hWndCtl, TEXT("Normal")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_NORMAL);

n = ComboBox_AddString(hWndCtl, TEXT("Below normal")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_BELOW_NORMAL);

n = ComboBox_AddString(hWndCtl, TEXT("Lowest")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_LOWEST);

n = ComboBox_AddString(hWndCtl, TEXT("Idle")); ComboBox_SetItemData(hWndCtl, n, THREAD_PRIORITY_IDLE);

ComboBox_SetCurSel(hWndCtl, nNormal);

272 Часть II. Приступаем к работе

Edit_LimitText(GetDlgItem(hWnd, IDC_SLEEPTIME), 4); // Maximum of 9999 return(TRUE);

}

///////////////////////////////////////////////////////////////////////////////

void Dlg_OnCommand (HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {

switch (id) { case IDCANCEL:

PostQuitMessage(0);

break;

case IDC_PROCESSPRIORITYCLASS:

if (codeNotify == CBN_SELCHANGE) { SetPriorityClass(GetCurrentProcess(), (DWORD)

ComboBox_GetItemData(hWndCtl, ComboBox_GetCurSel(hWndCtl)));

}

break;

case IDC_THREADRELATIVEPRIORITY:

if (codeNotify == CBN_SELCHANGE) { SetThreadPriority(GetCurrentThread(), (DWORD)

ComboBox_GetItemData(hWndCtl, ComboBox_GetCurSel(hWndCtl)));

}

break;

case IDC_SUSPEND:

//To avoid deadlock, call EnableWindow before creating

//the thread that calls SuspendThread. EnableWindow(hWndCtl, FALSE);

HANDLE hThreadPrimary; DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),

GetCurrentProcess(), &hThreadPrimary, THREAD_SUSPEND_RESUME, FALSE, DUPLICATE_SAME_ACCESS);

DWORD dwThreadID; CloseHandle(chBEGINTHREADEX(NULL, 0, ThreadFunc,

hThreadPrimary, 0, &dwThreadID)); break;

}

}

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 273

///////////////////////////////////////////////////////////////////////////////

INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

switch (uMsg) {

chHANDLE_DLGMSG(hWnd, WM_INITDIALOG,

Dlg_OnInitDialog);

chHANDLE_DLGMSG(hWnd, WM_COMMAND,

Dlg_OnCommand);

}

 

 

 

return(FALSE);

 

}

///////////////////////////////////////////////////////////////////////////////

class CStopwatch { public:

CStopwatch() { QueryPerformanceFrequency(&m_liPerfFreq); Start();

}

void Start() { QueryPerformanceCounter(&m_liPerfStart); }

__int64 Now() const { // Возвращает время (в мс) с момента вызова Start LARGE_INTEGER liPerfNow;

QueryPerformanceCounter(&liPerfNow); return(((liPerfNow.QuadPart - m_liPerfStart.QuadPart) * 1000)

/ m_liPerfFreq.QuadPart);

}

 

private:

 

LARGE_INTEGER m_liPerfFreq;

// отсчетов/с

LARGE_INTEGER m_liPerfStart;

// начальный счет

};

 

 

 

__int64 FileTimeToQuadWord (PFILETIME pft) { return(Int64ShllMod32(pft->dwHighDateTime, 32) | pft->dwLowDateTime);

}

int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int) {

HWND hWnd =

CreateDialog(hInstExe, MAKEINTRESOURCE(IDD_SCHEDLAB), NULL, Dlg_Proc);

BOOL fQuit = FALSE;

274 Часть II. Приступаем к работе

while (!fQuit) {

MSG msg;

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

// IsDialogMessage allows keyboard navigation to work properly. if (!IsDialogMessage(hWnd, &msg)) {

if (msg.message == WM_QUIT) {

fQuit = TRUE; // For WM_QUIT, terminate the loop.

}else {

//Not a WM_QUIT message. Translate it and dispatch it. TranslateMessage(&msg);

DispatchMessage(&msg);

}

}// if (!IsDialogMessage())

}else {

//Add a number to the listbox static int s_n = -1;

TCHAR sz[20];

StringCchPrintf(sz, _countof(sz), TEXT("%u"), ++s_n); HWND hWndWork = GetDlgItem(hWnd, IDC_WORK);

ListBox_SetCurSel(hWndWork, ListBox_AddString(hWndWork, sz));

//Remove some strings if there are too many entries

while (ListBox_GetCount(hWndWork) > 100) ListBox_DeleteString(hWndWork, 0);

// How long should the thread sleep

int nSleep = GetDlgItemInt(hWnd, IDC_SLEEPTIME, NULL, FALSE); if (chINRANGE(1, nSleep, 9999))

Sleep(nSleep);

}

}

DestroyWindow(hWnd);

return(0);

}

//////////////////////////////// End of File //////////////////////////////////

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 275

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

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

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

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

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

BOOL SetProcessAffinityMask(

HANDLE hProcess,

DW0RD_PTR dwProcessAffinityMask);

В первом параметре, hProcess, передается описатель процесса. Второй параметр, dwProcessAfjHnityMask, — это битовая маска, указывающая, на каких

276 Часть II. Приступаем к работе

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

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

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

B00L GetProcessAffinityMask(

HANDLE hProcess,

PDWORD_PTR pdwProcessAffinityMask,

PDW0RD_PTR pdwSystemAffinityMask);

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

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

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

DW0RD_PTR SetThreadAffinityMask(

HANDLE hThread,

DW0RD_PTR dwThreadAffinityMask);

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

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

//потоки 1, 2, 3 выполняются на процессорах 1, 2, 3

SetThreadAffinityMask(hThread1, 0x0000000E);

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 277

SetThreadAffinityMask(hThread2, 0х0000000Е);

SetThreadAffinityMask(hThread3, 0х0000000Е);

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

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

Поток

Приоритет

Маска привязки

Результат

А

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 передается описатель потока. В отличие от функций, которые мы уже рассматривали, параметр dwIdealProcessor содержит не битовую маску, а целое значение в диапазоне 0-31, которое указывает предпочтительный процессор для данного потока. Передав в нем константу

278 Часть II. Приступаем к работе

MAXIMUM_PROCESSORS (в WinNT.h она определена как 32 для 32-разрядных систем и 64 — для 64-разрядных), вы сообщите системе, что потоку не требуется предпочтительный процессор. Функция возвращает установленный ранее номер предпочтительного процессора или MAXIMUM_PROCESSORS, если таковой процессор не задан.

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

ными в ImageHlp.h:

// загружаем EXE-файл в память

PLOADED_IMAGE pLoadedlmage = ImageLoad(szExeName, NULL);

//получаем информацию о текущей загрузочной конфигурации EXE-файла

IMAGE_LOAD_CONFIG_DIRECTORY ilcd; GetImageConfigInformation(pLoadedlmage, &ilcd);

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

ilcd.ProcessAffinityMask = 0x00000003; // I desire CPUs 0 and 1

//сохраняем новую информацию о загрузочной конфигурации

SetImageConfigInformation(pLoadedlmage, &ilcd);

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

ImageUnload(pLoadedlmage);

Детально описывать эти функции я не стану — при необходимости вы найдете их в документации Platform SDK.

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

При запуске Windows Vista на машине с процессорами типа х86 можно ограничить число процессоров, используемых системой. Во время загрузки система анализирует содержимое хранилища загрузочной конфигурации (boot configuration data, BCD), заменившего файл boot.ini. BCD предоставляет уровень абстрагирования для оборудования и микрокода компьютера. Подробные сведе-

ния об этой новинке см. по ссылке http://www.microsoft.com/whdc/system/ platform/firmware/bcd.mspx.

Глава 7. Планирование потоков, приоритет и привязка к процессорам.docx 279

Программное конфигурирование BCD осуществляется средствами Windows Management Instrumentation (WMI), но некоторые общие параметры можно настроить и через графический интерфейс. Так, чтобы ограничить число процессо-

ров, доступных Windows, откройте Control Panel и щелкните Administrative Tools и выберите System Configuration. На вкладке Boot щелкните кнопку Advanced,

пометьте флажком параметр Number Of Processors и введите нужное число процессоров в соответствующее поле.

Соседние файлы в предмете Программирование на C++