Utilisation de threads.

Lancer deux processus légers en parallèle sous Windows.

CreateThread

Voir version :

Pas de dépendances

Télécharger :

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

unsigned long WINAPI fonc1(void* params)
{
    while(1)
    {
        printf("1\n");
    }
    return 0;
}

unsigned long WINAPI fonc2(void* params)
{
    while(1)
    {
        printf("2\n");
    }
    return 0;
}

int main()
{
    HANDLE h1;
    HANDLE h2;
    h1=CreateThread(NULL,0,fonc1,NULL,0,0);
    h2=CreateThread(NULL,0,fonc2,NULL,0,0);

    system("PAUSE");
    //TerminateThread(h1,0);
    //system("PAUSE");
    return 0;
}



Commentaires


  Bienvenue dans le monde du parallélisme

  Un petit cours s'impose avant meme de lancer le programme :

  Ici, nous allons parler de systeme, et particulierement de Windows.
  Les Linuxiens retourveront des fonctions similaires en incluant unistd.h au lieu de windows.h
  Mais ce tuto s'adresse aux personnes sous Windows.

  D'abord, il faut comprendre le principe :

  Windows sait faire fonctionner plusieurs programmes en meme temps...
  En effet, vous naviguez sur Internet, pendant ce temps, vous écoutez votre musique préférée,
  et, pendant le meme temps, vous gravez des trucs, imprimez des trucs, etc...
  Il s'agit la de plusieurs programmes que votre systeme sait lancer en meme temps...

  En meme temps ? 
  Pas tout a fait !!
  En réalité, si 2 programmes tournent en meme temps, 
  l'un est lancé pendant une fraction de secondes, puis interrompu,
  puis le second est lancé pendant une fraction de secondes, puis interrompu, 
  puis de nouveau le premier, etc...

  Si vous avez 50 programmes qui tournent en meme temps, c'est la meme chose :
  l'un est lancé, puis interrompu, puis un autre lancé, etc etc !!

  Chaque "programme" lancé est appelé "processus"
  si vous appuyez sur CTRL+ALT+SUPP, vous avez un onglet "processus" avec tous les processus 
  actuellement lancés dans Windows, ainsi que la mémoire qu'ils utilisent et le temps processeur
  qu'ils utilisent...
  En effet, chaque processus utilise sa propre zone de mémoire.

  Ce qui gere les processus, c'est l'ordonanceur (en anglais : scheduler)
  c'est le processus qui va décider lequel des autres faire passer en premier, gérer les priorités
  (car vous pouvez décider qu'un processus sera + prioritaire qu'un autre) et d'autres techniques
  sympa que nous n'aborderons pas trop ici, qui servent a faire le meilleur scheduler possible, afin
  que vos programmes puissent tourner correctement :)



  Dans ce tuto, nous allons parler des processus légers, c'est a dire des threads :
  En effet, au lieu de créer des processus indépendant (ce que nous verrons dans d'autres tutos)
  nous allons créer des threads, c'est a dire des mini processus, qui fonctionnent donc en parallelele,
  et indépendament, comme un processus, MAIS qui partage la meme zone de mémoire...
  Le thread est également un esclave du processus de votre programme :
  c'est a dire que quand vous faites un programme, et que vous le lancez, ça crée un processus, qui
  se détruit quand vous quittez votre programme (comme tout autre programme)
  un thread sera créé dans votre programme, et sera en dessous, si vous quittez votre programme,
  tous les threads que vous aurez créé seront détruits...

  Si vous appuyez sur CTR+ALT+SUPP, onglet performances, vous avez, dans le groupe "totaux" le nombre
  de threads que Windows est en train de gérer. (exemple sous Windows XP, si vous etes sous un autre 
  Windows, peut etre que vous ne pourrez pas le voir de cette maniere, mais les threads sont gérés)


  Procédons au programme :

  Vous pouvez remarquer qu'on inclue windows.h --> en effet, on va utiliser windows purement,
  ainsi que son mécanisme de gestion des programmes donc on va inclure windows.h

  Pour le moment, regardons le main :

  on crée 2 HANDLE : c'est un type de windows qui permet d'identifier un programme, un identificateur :
  chacun de nos threads 

  Ensuite, on va créer un nouveau thread :

	h1=CreateThread(NULL,0,fonc1,NULL,0,0);

  le nouveau thread est créé. 
  A quoi servent tous ces parametres ?
  Je dirais que le seul important est "fonc1" (si vous ne voulez pas vous casser la tete, utiliser cela.)
  Pour + d'explications sur ces parametres :
  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcekernl/html/_wcesdk_win32_resumethread.asp

  Donc cette fonction crée un nouveau thread, qui se lancera sur la fonction fonc1

  Ensuite, meme chose pour fonc2 dans l'handle h2.

  Ensuite, croyez moi, mais le processus se bloque, tout de suite, sur le system("PAUSE");
  Donc en theorie, le programme devrait attendre sagement ici ??

  Pourtant, lancez le : il affiche des 1 et des 2 sans arret...
  Et pour garantir qu'il est bien sur le system("PAUSE"), appuyez donc sur entrée : le programme
  s'arrete...

  (Note sur les fonctions fonc1 et fonc2
  Vous avez vu qu'elles sont déclarées :

  unsigned long WINAPI fonc1(void* params)

  le WINAPI entre le type retourné, (unsigned long) qui sera le code de retour du thread,
  et fon1, le nom de fonction, est propre a WINDOWS, j'avoue que je ne sais pas bien pourquoi est
  ce que WINDOWS a besoin de cela... en tout cas, une fonction d'entrée d'un thread doit etre 
  déclarée comme ça...
  Par contre, si dans votre thread, vous appelez d'autres fonctions, vous n'etes pas obligé de mettre
  cela par la suite, programmez normalement :)
  Le void* params, c'est si vous voulez envoyez des parametres lors de la création de threads...
  Ces parametres sont a passer par un pointeur passé dans createthreads, le 4e parametre, qu'on passe a NULL dans
  notre exemple.)

  Bref,
  
  Donc expliquons ce qu'il se passe :
  Comme vous pouvez vous en douter, 2 threads sont en route :
  fonc1 et fonc2, qui, comme vous pouvez le constater, affichent des 1 et des 2 sans arret,
  en boucle !! (while(1) faisant une boucle qui ne s'arrete jamais...)

  Voila donc comment ça se passe !
  Le processus du programme attend que vous appuyez sur entrée, pendant que les 2 threads écrivent
  des 1 et des 2 sans arret...


  Relancez le programme sans arret, ne constatez vous pas de choses bizarres ?
  En effet, des fois, on a bien une ligne ou on a "1", une autre ou on a "2"
  mais des fois, on a "12" ou "21" sur la meme ligne !!!
  des fois, on a des lignes sans rien du tout !!

  Comment ça se fait ?
  --> l'ordonanceur a fait fonctionner ces threads tour par tour, pendant un laps de temps chacun
  avant de passer a son suivant.
  Et, N'IMPORTE QUAND, windows peut dire a un thread "toi stop, au suivant !"
  n'importe quand !!

  De ce fait, un "12" est simplement le thread h1 qui a marché, qui a écrit son 1, et qui a été interrompu
  avant d'écrire son ENDL...
  donc le curseur n'est pas revenu a la ligne, et quand le thread 2 commence, il écrit donc le 2 a coté...

  Et quand on a une ligne sans rien ?
  Et bien par exemple le thread 1 en était a 1 et n'avait pas eu le temps de faire son ENDL...
  le thread 2 commence, il fait plusieurs tours de while, et fini bien, lui par son ENDL, donc
  la derniere ligne ecrite est "2" puis il est revenu a la ligne...
  Le thread 1 reprend : il reprend a son endl qu'il n'avait pas pu faire :
  --> ça saute une ligne !!
  puis il continue l'itération suivante : il écrit son "1" etc....


Activons maintenant les ligns suivantes mises en commentaire :

  TerminateThread(h1,0);
  system("PAUSE");

  Lancez le programme, appuyez une premiere fois sur entrée : vous ne voyez ensuite que des 2
  s'afficher !!  puis si vous rappuyez, ça quitte...

  En effet, apres le premier entier, vous demandez au thread h1 de se finir, donc il n'y a plus
  que le h2 qui tourne toujours, ce qui explique qu'il n'y aie plus que des 2...
  Le "0" passé en parametre est le code de fermeture que vous donnez au thread. Si vous ne savez pas
  quoi passer, passez 0.

  * Je tiens a préciser que fermer un thread est violent :
  en effet, si votre thread, qui est un programme a part entiere, est fermé, il l'est d'un seul coup,
  et est donc killé comme quand vous killez un processus depuis Windows : il est beaucoup plus propre
  de faire se faire quitter le thread de lui meme, plutot que ce soit le processus, ou un autre thread,
  qui le tue...

Faisons d'autres expériences :

  Au lieu de faire 
  TerminateThread(h1,0);

  essayez de faire :
  SuspendThread(h1);  // vous suspendez l'exécution d'un thread (mise en pause), sans pour autant le tuer...

  Puis, apres un system("PAUSE"); 
  ResumeThread(h1);  // vous le faites reprendre ! enlevez la pause.

  Note :
  Si vous créez le thread ainsi :

  h1=CreateThread(NULL,0,fonc1,NULL,CREATE_SUSPENDED,0);
  alors le thread est crée, mais mis en pause, il vous faudra un ResumeThread pour le lancer.
  Cela peut etre pratique si vous voulez faire partir les threads en meme temps :
  Vous les créez en mode pause, puis vous lancez les ResumeThread...

  Essayez maintenant de mettre :

  SetThreadPriority(h1,THREAD_PRIORITY_ABOVE_NORMAL);
  juste apres les Create

  Vous constaterez que vous n'avez presque que des "1" a l'écran, quelques "2" de temsp en temps
  mais rare !!
  Pourquoi ?
  Parce que vous venez de dire que h1 était prioritaire !!

  Vous pouvez définir la priorité d'un thread avec cette fonction, et l'une des options suivantes :

  THREAD_PRIORITY_TIME_CRITICAL
  THREAD_PRIORITY_HIGHEST
  THREAD_PRIORITY_ABOVE_NORMAL
  THREAD_PRIORITY_NORMAL
  THREAD_PRIORITY_BELOW_NORMAL
  THREAD_PRIORITY_LOWEST
  THREAD_PRIORITY_ABOVE_IDLE
  THREAD_PRIORITY_IDLE

  Par défaut, la valeur est "THREAD_PRIORITY_NORMAL".
  Comme vous pouvez le voir ci dessus, j'ai mis la valeur juste au dessus, pour dire que h1 etait
  un peu plus prioritaire...

  Plus on monte dans ces valeurs, plus le thread est prioritaire...
  Plus on descend dans ces valeurs, moins il l'est...

  ATTENTION : je vous déconseille d'utiliser THREAD_PRIORITY_TIME_CRITICAL :
  si vous mettez cette valeur, vous dites a votre ordi que ce thread est le plus important qui soit.
  et du coup, il va l'executer au détriment de tous les autres : y compris le processus qui teste 
  le system("PAUSE"); en gros, l'ordi va passer quasi 100% de son temps a vous afficher les 1,
  et ignorer tout le reste : vous reprendrez difficilement la main, il ne vous reste qu'a redémarrer...
  Si vous voulez vous rendre compte de cela, essayez déja THREAD_PRIORITY_HIGHEST, et constatez que la
  musique que vous écoutez en meme temps se met a saccader car Windows s'en occupe tres peu, s'axant un
  max sur le thread h1...

  Au contraire, si vous mettez la priorité en THREAD_PRIORITY_IDLE, le thread sera quasiment jamais fait,
  uniquement quand Windows n'aura vraiment rien d'autre a faire, ce qui est tres rare...

  A vous de doser cela correctement !!


Autres expériences :

  Lorsque vos threads sont lancés, regardez, avec CTRL+ALT+SUPP, l'état de votre processeur :
  Il est utilisé a 100%
  et principalement par le programme :	csrss.exe
  qui sert a gérer les threads.
  En effet, vos threads affichent leurs 1 et 2 tant qu'ils peuvent : des qu'ils ont la main, 
  ils la gardent jusqu'a ce qu'ils soient interrompus, bref, travaillent a fond !!
  Et votre pauvre processeur travaille donc beaucoup....

  On va donc dire aux threads de laisser de temps en temps souffler le pross :

  rajoutez, en dessous des cout de fonc1 et fonc2 :

  Sleep(1);

  --> Vous dites au thread de "dormir" pendant 1 millieme de secondes...
  Et ainsi, pendant qu'il dort, le pross s'occupe des autres taches de windows...
  Vous verrez que votre pross n'est plus harcelé...

  Il est important, quand le thread n'a rien a faire, de le faire dormir, meme si ce n'est qu'un
  millieme de secondes, pour décharger le processeur.

  Note : on constate qu'on a toujours des "12" : il suffit que le thread se réveille juste un 
  micro-chouia de fraction de seconde avant que le scheduler ,ne se décide a dire "au suivant", et
  le thread arrive a ce moment la, et ne pourra afficher que son "1" et etre interrompu...

  De toute façon, la façon dont l'ordonnanceur (scheduler) de Windows agit, est propre a votre Windows,
  le programmeur n'a pas a s'en occuper, c'est a Windows de gérer ses processus et ses threads :
  tout ce qu'il y a a savoir la dessus, c'est que Windows peut vous couper n'importe quand, vous ne
  pouvez pas prévoir...

  Ici, c'est genant, car on fait écrire les 2 threads au meme endroit, mais on verra par la suite que
  ce n'est pas grave du tout... Que des fois, il y a des précautions a prendre, mais que tout est géré :)