Le planificateur de threads Windows est injuste?

Parfois, quand je lance ce programme simple

#include  DWORD WINAPI ThreadStart(LPVOID) { for (;;) { } return 0; } int _tmain() { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--) { CloseHandle(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL)); } Sleep(INFINITE); } 

J’observe une planification des fils constamment injuste, telle que:

Capture d'écran

Ce n’est pas injuste à chaque exécution, mais quand c’est le cas, cela rest injuste pendant toute la durée de vie de l’application.

Pourquoi cela se produit-il et quelle est la bonne façon de l’éviter?

Quelques options:


Si (et seulement si) vous exécutez Windows 7/8 64 bits (pas 32 bits), ou Windows Server 2008 R2 ou version ultérieure, vous pouvez complètement ignorer le planificateur système et vous en occuper vous-même, même s’il ne s’agit pas d’un entreprise sortingviale!

Il est décrit sur MSDN ici et s’appelle la planification en mode utilisateur (UMS) .

Capture d’écran partielle de MSDN ci-dessous – est-ce une utilisation pour vous?

entrer la description de l'image ici

En outre, il peut être judicieux de désactiver l’hyper-threading dans votre BIOS (le cas échéant) car il existe un débat sur la manière dont Windows fait la distinction entre les cœurs logiques et physiques. Voir ici pour une discussion .

Il y a aussi quelques fonctions disponibles comme SetThreadAffinityMask() ( MSDN ici ) et SetThreadIdealProcessor() qui peuvent aider, même si personnellement, j’ai trouvé cela un peu SetThreadIdealProcessor() . Il est plus fréquent que je sape le rendement global plutôt que de l’aider.

Vous verrez cela sur un système multiprocesseur chaque fois qu’un thread ne fait pas partie de votre programme. Supposons qu’une tâche système démarre pendant quelques secondes, en analysant des assemblages .NET ou quelque chose du genre. Il fera tomber un de vos threads de la planification pendant un certain nombre de tranches de temps pendant que les threads sur les autres cœurs continuent de s’exécuter. Le thread qui a été éliminé ne rattrapera jamais les autres.

Ceci est un coup dans la conjecture sombre.

Mais je vais suggérer que Hyper-Threading peut fausser un peu les résultats.

Si le bios / cmos de votre ordinateur portable permet de désactiver l’hyperthreading comme certains ordinateurs, il serait intéressant de voir les numéros avec votre code s’exécuter uniquement sur des cœurs réels.

Et même si HT est la raison pour laquelle il fait la part belle aux threads, alors je ne sais toujours pas pourquoi.

Sur mon système, SetThreadAffinityMask semble atténuer le problème. Il y a toujours un certain déséquilibre, apparemment dû au fait qu’un kernel a moins de temps disponible que l’autre, mais ce n’est pas aussi grave.

Windows ne semble pas disposé à déplacer ces threads entre les cœurs; ils changent rarement de kernel après la première seconde environ. Si les threads ne sont pas répartis de manière égale entre les cœurs, les durées de thread le reflètent.

C’est le code que j’ai utilisé pour essayer des masques d’affinité:

 #include  #include  DWORD WINAPI ThreadStart(LPVOID arg) { DWORD pn1, pn2; printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1 = GetCurrentProcessorNumber()); // The problem comes back if you enable this line // SetThreadAffinityMask(GetCurrentThread(), -1); for (;;) { for (int i = 0; i < 10000; i++); pn2 = GetCurrentProcessorNumber(); if (pn2 != pn1) { pn1 = pn2; printf("Thread %u on processor %u\n", GetThreadId(GetCurrentThread()), pn1); } } return 0; } int main(int argc, char ** argv) { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = 0; i < si.dwNumberOfProcessors; i++) { SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i); SetThreadAffinityMask(CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL), 1 << i); } Sleep(INFINITE); return 0; } 

Cette approche semble également atténuer le problème, mais peut-être pas aussi efficacement:

 #include  #include  DWORD WINAPI ThreadStart(LPVOID arg) { for (;;); return 0; } int main(int argc, char ** argv) { SYSTEM_INFO si; GetSystemInfo(&si); for (DWORD i = si.dwNumberOfProcessors * 2; i > 0; i--) { CreateThread(NULL, 0, &ThreadStart, NULL, 0, NULL); } for (;;) { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); Sleep(100); SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); } return 0; } 

Je soupçonne que nous examinons une sorte de mesure d'économie d'énergie conçue en partant du principe que les processus à faible priorité ne nécessitent pas une planification équitable. (On peut dire que définir un thread ou un processus avec une faible priorité indique "Je ne me soucie pas de la quantité de CPU que je reçois.")