Bien programmer en langage C
Date de publication : 28 mai 2008
    
    
	
	
	
	
	
	
	
	
	
	
	IX. POSIX threads
			
			IX-A. Systèmes multi-tâches
				
				IX-A-1. Introduction
					
					
					
					
					
				
				IX-A-2. Processus
					
					
					
					
					
					IX-A-2-a. Exécution
						
						
						
						
						
						
						
					
					IX-A-2-b. Création dynamique de processus
						
						
						
						
					
				
				IX-A-3. Processus légers
					
					
					
					
					
				
			
			IX-B. Introduction
				
				
				IX-B-1. Note pour Windows.
					
					
				
				IX-B-2. Objectif
					
					
				
			
			IX-C. Hello worlds
				
				
				
				
				
				
				
				
				
				
				
				
				
			
			IX-D. Données
				
				
				
				IX-D-1. Éviter les globales 'à-la-barbare'
					
					
					
				
				IX-D-2. Le cas de l'écrivain et du lecteur
					
					
					
					
					
					
					
					
					
				
			
			IX-E. Synchronisation
				
				
				
				
			
			IX-F. Résumé des types et des fonctions utilisés
				
				
				
				
				
			
			IX-G. Ressources
				
				
			
		
	
    
    
	
	
	
	
	
	
	
	
	
	
	IX. POSIX threads
			
			IX-A. Systèmes multi-tâches
				
				IX-A-1. Introduction
					
					
						Force est de constater qu'un programme courant (sauf traitements lourds comme un scan d'antivirus) passe 
						90 à 99% de son temps à attendre des évènements extérieurs, notamment, évidemment, les entrées 
						(stdin, lecture fichier, attentes de données reçues sur un socket...) à moins qu'on ait voulu délibérément 
						retarder l'exécution de telle ou telle action. 
					
					
						Donc, un processeur est la plupart du temps en train de ne rien faire (ou alors des boucles inutiles). 
						Dès les débuts de l'informatique, cette constatation a amené les informaticiens à réfléchir à la 
						meilleure façon d'utiliser ce temps libre. 
					
					
						L'idée de répartir le temps du processeur entre plusieurs application s'est alors imposée. 
					
					
						Les performances des systèmes modernes reposent en grande partie sur leur possibilité de charger et exécuter 
						plusieurs applications 'en même temps'. Cette simultanéité n'est évidemment qu'apparente, sur une 
						machine mono-processeur. Mais elle a l'avantage d'exister et d'être efficace, et nous le constatons 
						tous les jours sur notre PC ou notre téléphone mobile. 
					
				
				IX-A-2. Processus
					
					
						Un processus est une application (ou un programme) en cours d'exécution. Un programme, est avant tout 
						un fichier stocké sur un disque quelconque contenant du code exécutable. 
					
					
						Pour pouvoir être exécuté, il faut réunir un certain nombre de conditions : 
					
					
						- 
							charger le programme en mémoire.
						
 
						- 
							réserver de l'espace pour les données statiques.
						
 
						- 
							réserver de l'espace dans la pile.
						
 
						- 
							initialiser les pointeurs d'instructions et de pile.
						
 
						- 
							lancer l'exécution.
						
 
					
					
						A un processus est donc associé un espace mémoire composé d'un programme (code exécutable) et de données 
						(statiques et pile). 
					
					IX-A-2-a. Exécution
						
						
							Dans un système multi-tâches comme Unix ou Windows (mais pas MS-DOS), "lancer l'exécution" signifie rendre 
							le processus "éligible", c'est à dire que dès le le processeur est libre, il pourra l'exécuter 
							réellement (il devient alors "élu"). 
						
						
							Dès que le programme rencontre une fonction qui le fait 'attendre' (suspension temporisée, attente d'une 
							entrée etc.) le système (en fait, l'ordonnanceur, qui est le mécanisme de répartition du temps 
							machine) en profite pour suspendre l'exécution du processus ("suspendu"). Le premier processus 
							éligible passe alors en mode "élu". (il "prend la main" comme on dit vulgairement). 
						
						
							Il faut savoir qu'il existe certaines tâches dites 'immédiates' (interruptions) qui peuvent s'exécuter 
							en temps réel à chaque instant. C'est notamment le cas pour les traitements de bas niveau de type 
							timer (déterminer une échéance) ou clavier, réseau, disque etc. Les évènements détéctés sont transmis 
							au système qui détermine alors à quel processus suspendu ils correspondent et si il s'agit bien 
							d'un évènement de déblocage. ("en attente d'un <enter>", "en attente d'une trame réseau" 
							etc). Si c'est le cas, le processus passe en état "éligible". La suite, on la connait. 
						
						
							En résumé, les états d'un processus sont : 
						
						
						
							Il peut y avoir des schémas plus complexes, mais le principe est là. 
						
					
					IX-A-2-b. Création dynamique de processus
						
						
							Il est possible par logiciel de créer une 'copie' ou un 'fils' du processus courant. Cela revient à créer 
							un nouvel espace mémoire contenant une copie des données (le code reste unique). 
						
						
							Les valeurs des données du processus fils sont celles de l'instant de la copie (sous unixoïde, fork()). 
							Si le processus fils modifie ses données, les données du père sont inchangées. De même, si le 
							père modifie ses données, les données du fils sont inchangées. Les deux espaces mémoires sont 
							complètement étanches. au point que si il fallait échanger des données entre les deux processus, 
							il faudrait utiliser des mécanismes assez complexes (IPC : Inter Processus Communication, SHM 
							: SHared Memory, sockets UNIX etc). 
						
						
							N'étant pas spécialiste des processus, j'ai peut être commis des erreurs... Remarques 
							bienvenues.
						
					
				
				IX-A-3. Processus légers
					
					
						Dans un processus donné, il est possible de créer un processus léger appelé aussi thread qui est un espace 
						mémoire limité à une simple pile et un code exécutable (la fonction du thread). La mémoire statique 
						est celle du processus courant. Si plusieurs threads sont crées, ils partagent le même espace de 
						mémoire statique, mais ils ont chacun leur propre pile (variables locales). 
					
					
						Ce mécanisme est simple et permet d'écrire facilement des applications multi-tâches. Les données statiques 
						étant partagées, leur accès est direct (mais on évite les globales, on n'est pas des barbares) 
						
					
					
						Revers de la médaille, les accès aux données étant directs, et des accès concurrents étant possibles, 
						les données peuvent être incorrectes si des précautions ne sont pas prises. Il existe donc des 
						mécanismes de protection qui permettent de réguler l'accès aux données (sémaphores). 
					
					
						La gestion des threads a été normalisée par POSIX.1. C'est l'API pthreads. 
					
				
			
			IX-B. Introduction
				
				
					La norme POSIX.1 fournit un ensemble de primitives permettant de réaliser des processus légers. Contrairement 
					aux processus habituels, la mémoire est commune. Ces processus légers sont communément appelés 'threads' 
					ou 'tâches'. Selon l'implémentation, l'ensemble des primitives est, par exemple, regroupé dans la 
					bibliothèque libpthread.a (gcc) qu'il faut bien sûr penser à ajouter au projet (-lpthread). 
				
				IX-B-1. Note pour Windows.
					
					
						Les pthreads sont disponibles 
ici . 
					
 
				
				IX-B-2. Objectif
					
					
						Le but de cet article est de fournir les bases de l'utilisation de la bibliothèque pthread par une approche 
						pragmatique. 
					
				
			
			IX-C. Hello worlds
				
				
					Il est simple de réaliser la création, l'exécution et la fin de 2 tâches 'concurrentes'[
1]. 
					Pour cela, on utilise le type opaque (ADT) pthread_t qui va contenir le contexte de chaque tâche, 
					et une fonction de création de tâche pthread_create() qui va associer la tâche avec son contexte, 
					et informer l'ordonnanceur du système de son existence. L'ordonnanceur lancera la tâche dès que 
					possible 
				
 
#include <stdio.h>
#include <pthread.h>
static void *task_a (void *p_data)
{
   puts ("Hello world A");
   (void) p_data;
   return NULL;
}
static void *task_b (void *p_data)
{
   puts ("Hello world B");
   (void) p_data;
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   puts ("main init");
   pthread_create (&ta, NULL, task_a, NULL);
   pthread_create (&tb, NULL, task_b, NULL);
   puts ("main end");
   return 0;
}
  | 
 
				
					La sortie est, par exemple, celle-ci. Attention, l'ordre dans lequel l'exécution des taches se font n'est 
					pas défini (normal, puisque dans l'idéal, elle sont concurrentes). 
				
main init
main end
Hello world A
Hello world B
  | 
 
				
					Il est important de bien interpréter les lignes produites. Dans cet exemple, il y a un processus (l'application) 
					et 3 tâches : 
				
				
				
					Il est clair, à la vue des lignes produites, que la tâche main() se termine avant que les autres ne soient 
					lancées. Cela signifie que, étant donné que le contexte des tâche n'existe plus, le comportement 
					des tâches devient indéterminé. C'est pourquoi il existe la fonction de synchronisation pthread_join() 
					qui permet de suspendre l'exécution de la tâche courante en attendant la fin d'une tâche déterminée. 
					
				
#include <stdio.h>
#include <pthread.h>
static void *task_a (void *p_data)
{
   puts ("Hello world A");
   (void) p_data;
   return NULL;
}
static void *task_b (void *p_data)
{
   puts ("Hello world B");
   (void) p_data;
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   puts ("main init");
   pthread_create (&ta, NULL, task_a, NULL);
   pthread_create (&tb, NULL, task_b, NULL);
#if 1
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
#endif
   puts ("main end");
   return 0;
}
  | 
 
				
					La sortie donne maintenant ceci: 
				
main init
Hello world A
Hello world B
main end
  | 
 
				
					Dans la réalité, une tâche est généralement une boucle (infinie ou non). 
				
#include <stdio.h>
#include <pthread.h>
static void *task_a (void *p_data)
{
   int i;
   for (i = 0; i < 5; i++)
   {
      printf ("Hello world A (%d)\n", i);
   }
   (void) p_data;
   return NULL;
}
static void *task_b (void *p_data)
{
   int i;
   for (i = 0; i < 7; i++)
   {
      printf ("Hello world B    (%d)\n", i);
   }
   (void) p_data;
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   puts ("main init");
   pthread_create (&ta, NULL, task_a, NULL);
   pthread_create (&tb, NULL, task_b, NULL);
#if 1
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
#endif
   puts ("main end");
   return 0;
}
  | 
 
				
					Les informations produites sont variables. Par exemple : 
				
main init
Hello world A (0)
Hello world B    (0)
Hello world B    (1)
Hello world A (1)
Hello world A (2)
Hello world B    (2)
Hello world B    (3)
Hello world A (3)
Hello world A (4)
Hello world B    (4)
Hello world B    (5)
Hello world B    (6)
main end
  | 
 
				
					ou 
				
main init
Hello world A (0)
Hello world B    (0)
Hello world B    (1)
Hello world A (1)
Hello world A (2)
Hello world B    (2)
Hello world B    (3)
Hello world B    (4)
Hello world A (3)
Hello world A (4)
Hello world B    (5)
Hello world B    (6)
main end
  | 
 
				
					mais on constate que le mécanisme de rendez-vous fonctionne et que les deux boucles sont terminées correctement. 
					Sans le mécanisme de synchronisation : 
				
#if 0
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
#endif
  | 
 
				
					on aurait, par exemple, obtenu ceci, ce qui n'est pas loin d'une catastrophe... 
				
main init
main end
Hello world A (0)
Hello world A (1)
Hello world B    (0)
Hello world B    (1)
Hello world B    (2)
Hello world A (2)
Hello world A (3)
Hello world B    (3)
Hello world B    (4)
Hello world A
  | 
 
				
					[1] En fait, la concurrence est une notion relative. Sur un système mono-processeur, l'exécution des 
					tâches se fait selon des critères de temps (time slicing) ou de suspensions. 
				
			
			IX-D. Données
				
				
					L'application étant formée d'un seul processus, les données sont communes à toutes les tâches. Cela signifie 
					qu'une donnée est partageable directement entre les processus. Le terme 'données communes' recouvre 
					comme il se doit : 
				
				
					- 
						les données statiques
					
 
					- 
						les données allouées
					
 
					- 
						les données locales
					
 
				
				IX-D-1. Éviter les globales 'à-la-barbare'
					
					
						Il est possible d'utiliser le 4ème paramètre de pthread_create() pour passer l'adresse d'un objet quelconque 
						à une tâche (variable simple, tableau, structure) au moment de la création de la tâche. Il est 
						bien évident que la durée de vie de cette variable doit être supérieure ou égale à la durée de 
						vie de la tâche qui l'utilise. 
					
#include <stdio.h>
#include <pthread.h>
static void *task_a (void *p_data)
{
   if (p_data != NULL)
   {
      
      char const *s = p_data;
      int i;
      for (i = 0; i < 5; i++)
      {
         printf ("'%s' (%d)\n", s, i);
      }
   }
   return NULL;
}
static void *task_b (void *p_data)
{
   if (p_data != NULL)
   {
      
      char const *s = p_data;
      int i;
      for (i = 0; i < 7; i++)
      {
         printf ("'%s'     (%d)\n", s, i);
      }
   }
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   puts ("main init");
   pthread_create (&ta, NULL, task_a, (char*)"Tache A");
   pthread_create (&tb, NULL, task_b, (char*)"Tache B");
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
   puts ("main end");
   return 0;
}
  | 
 
					
						qui produit par exemple : 
					
main init
'Tache A' (0)
'Tache B'     (0)
'Tache B'     (1)
'Tache A' (1)
'Tache A' (2)
'Tache A' (3)
'Tache B'     (2)
'Tache B'     (3)
'Tache B'     (4)
'Tache A' (4)
'Tache B'     (5)
'Tache B'     (6)
main end
  | 
 
				
				IX-D-2. Le cas de l'écrivain et du lecteur
					
					
						Soit une variable partagée qui est modifiée par une tâche, et lue par une autre. Le code 'naïf' suivant 
						: 
					
#include <stdio.h>
#include <pthread.h>
struct shared
{
   int data;
};
struct data
{
   
   int nb;
   char const *sid;
   
   struct shared *psh;
};
static void *task_w (void *p)
{
   if (p != NULL)
   {
      
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         int x = p_data->psh->data;
         x++;
         p_data->psh->data = x;
         printf ("'%s' (%d) data <- %d\n", p_data->sid, i, p_data->psh->data);
      }
   }
   return NULL;
}
static void *task_r (void *p)
{
   if (p != NULL)
   {
      
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         printf ("                        "
                 "'%s' (%d) data == %d\n", p_data->sid, i, p_data->psh->data);
      }
   }
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   struct shared sh =
   {
      .data = 0,
   };
   struct data da =
   {
      .nb = 5,
      .sid = "Writer",
      .psh = &sh,
   };
   struct data db =
   {
      .nb = 7,
      .sid = "Reader",
      .psh = &sh,
   };
   puts ("main init");
   pthread_create (&ta, NULL, task_w, &da);
   pthread_create (&tb, NULL, task_r, &db);
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
   puts ("main end");
   return 0;
}
  | 
 
					
						provoque des abérrations comme 
					
main init
'Writer' (0) data <- 1
                        'Reader' (0) data == 1
                        'Reader' (1) data == 2
'Writer' (1) data <- 2
'Writer' (2) data <- 3
                        'Reader' (2) data == 2
                        'Reader' (3) data == 4
'Writer' (3) data <- 4
'Writer' (4) data <- 5
                        'Reader' (4) data == 4
                        'Reader' (5) data == 5
                        'Reader' (6) data == 5
main end
  | 
 
					
						où l'on constate que les valeurs lues sont parfois fausses par rapport aux valeurs réelles. 
					
					
						Pour se prémunir contre ce problème, on doit évaluer quelle est la 'zone critique', et ensuite la protéger 
						avec un 'sémaphore'. C'est tout simplement un mécanisme qui va empêcher une autre tâche d'interrompre 
						la section critique. Dans l'environnement pthread, ce mécanisme est appelé mutex. Il met en oeuvre 
						le type opaque pthread_mutex_t et les fonctions pthread_mutex_lock() et pthread_mutex_unlock(). 
						
					
					
						Voici un exemple de protection de la section critique de l'écrivain. (empêcher le lecteur d'accéder en 
						lecture pendant l'écriture). 
					
#include <stdio.h>
#include <pthread.h>
struct shared
{
   int data;
   pthread_mutex_t mut;
};
struct data
{
   
   int nb;
   char const *sid;
   
   struct shared *psh;
};
static void *task_w (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         pthread_mutex_lock (&p_data->psh->mut);
         {
            int x = p_data->psh->data;
            x++;
            p_data->psh->data = x;
         }
         printf ("'%s' (%d) data <- %d\n", p_data->sid, i, p_data->psh->data);
         pthread_mutex_unlock (&p_data->psh->mut);
      }
   }
   return NULL;
}
static void *task_r (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         printf ("                        "
               "'%s' (%d) data == %d\n", p_data->sid, i, p_data->psh->data);
      }
   }
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   struct shared sh =
   {
      .data = 0,
      .mut = PTHREAD_MUTEX_INITIALIZER,
   };
   struct data da =
   {
      .nb = 5,
      .sid = "Writer",
      .psh = &sh,
   };
   struct data db =
   {
      .nb = 7,
      .sid = "Reader",
      .psh = &sh,
   };
   puts ("main init");
   pthread_create (&ta, NULL, task_w, &da);
   pthread_create (&tb, NULL, task_r, &db);
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
   puts ("main end");
   return 0;
}
  | 
 
					
						L'amélioration est flagrante, mais insuffisante : 
					
main init
'Writer' (0) data <- 1
'Writer' (1) data <- 2
'Writer' (2) data <- 3
                        'Reader' (0) data == 3
                        'Reader' (1) data == 4
'Writer' (3) data <- 4
'Writer' (4) data <- 5
                        'Reader' (2) data == 4
                        'Reader' (3) data == 5
                        'Reader' (4) data == 5
                        'Reader' (5) data == 5
                        'Reader' (6) data == 5
main end
  | 
 
					
						Il faut aussi protéger la lecture, c'est à dire empêcher l'écrivain d'écrire pendant la lecture : 
					
#include <stdio.h>
#include <pthread.h>
struct shared
{
   int data;
   pthread_mutex_t mut;
};
struct data
{
   
   int nb;
   char const *sid;
   
   struct shared *psh;
};
static void *task_w (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         pthread_mutex_lock (&p_data->psh->mut);
         {
            int x = p_data->psh->data;
            x++;
            p_data->psh->data = x;
         }
         printf ("'%s' (%d) data <- %d\n", p_data->sid, i, p_data->psh->data);
         pthread_mutex_unlock (&p_data->psh->mut);
      }
   }
   return NULL;
}
static void *task_r (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         pthread_mutex_lock (&p_data->psh->mut);
         printf ("                        "
               "'%s' (%d) data == %d\n", p_data->sid, i, p_data->psh->data);
         pthread_mutex_unlock (&p_data->psh->mut);
      }
   }
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   struct shared sh =
   {
      .data = 0,
      .mut = PTHREAD_MUTEX_INITIALIZER,
   };
   struct data da =
   {
      .nb = 5,
      .sid = "Writer",
      .psh = &sh,
   };
   struct data db =
   {
      .nb = 7,
      .sid = "Reader",
      .psh = &sh,
   };
   puts ("main init");
   pthread_create (&ta, NULL, task_w, &da);
   pthread_create (&tb, NULL, task_r, &db);
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
   puts ("main end");
   return 0;
}
  | 
 
					
						Le comportement est maintenant conforme aux attentes : 
					
main init
'Writer' (0) data <- 1
                        'Reader' (0) data == 1
'Writer' (1) data <- 2
                        'Reader' (1) data == 2
'Writer' (2) data <- 3
                        'Reader' (2) data == 3
'Writer' (3) data <- 4
                        'Reader' (3) data == 4
'Writer' (4) data <- 5
                        'Reader' (4) data == 5
                        'Reader' (5) data == 5
                        'Reader' (6) data == 5
main end
  | 
 
				
			
			IX-E. Synchronisation
				
				
					Nous avons pu constater qu'en environnement multi-tâches, l'ordre d'exécution des instructions de 2 tâches 
					différentes était indéterminé. C'est pourquoi il existe un mécanisme permettant la synchronisation. 
					Par exemple, bloquer la tâche courante en attente d'un évènement ou la débloquer dès le déclenchement 
					de cet évènement. 
				
				
					Dans l'environnement pthread, on parle de 'condition'. L'objet est pthread_cond_t, les fonctions sont 
					pthread_cond_wait() et pthread_cond_signal(). 
				
				
					Dans l'exemple initial, les deux tâches s'exécutaient de façon désordonnée. Voici un mécanisme de synchronisation 
					de la tâche B par la tâche A. Il a fallu ajouter une suspension dans la tâche A afin de laisser 
					du temps à B de s'exécuter. 
				
#include <stdio.h>
#include <pthread.h>
#ifdef WIN32
#include <windows.h>
#define msleep(ms) Sleep(ms)
#else
#include <unistd.h>
#define msleep(ms) usleep((ms * 1000))
#endif
struct shared
{
   pthread_mutex_t mut;
   pthread_cond_t synchro;
};
struct data
{
   
   int nb;
   char const *sid;
   
   struct shared *psh;
};
static void *task_a (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      struct shared *psh = p_data->psh;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         pthread_mutex_lock (&psh->mut);
         printf ("'%s' (%d)\n", p_data->sid, i);
         pthread_cond_signal (&psh->synchro);
         pthread_mutex_unlock (&psh->mut);
         msleep (1000);
      }
   }
   return NULL;
}
static void *task_b (void *p)
{
   if (p != NULL)
   {
      struct data *p_data = p;
      struct shared *psh = p_data->psh;
      int i;
      for (i = 0; i < p_data->nb; i++)
      {
         pthread_mutex_lock (&psh->mut);
         pthread_cond_wait (&psh->synchro, &psh->mut);
         printf ("                        "
                 "'%s' (%d)\n", p_data->sid, i);
         pthread_mutex_unlock (&psh->mut);
      }
   }
   return NULL;
}
int main (void)
{
   pthread_t ta;
   pthread_t tb;
   struct shared sh =
   {
      .mut = PTHREAD_MUTEX_INITIALIZER,
      .synchro = PTHREAD_COND_INITIALIZER,
   };
   struct data da =
   {
      .nb = 5,
      .sid = "task A",
      .psh = &sh,
   };
   struct data db =
   {
      .nb = 4,
      .sid = "Task B",
      .psh = &sh,
   };
   puts ("main init");
   pthread_create (&ta, NULL, task_a, &da);
   pthread_create (&tb, NULL, task_b, &db);
   pthread_join (ta, NULL);
   pthread_join (tb, NULL);
   puts ("main end");
   return 0;
}
  | 
 
			
			IX-F. Résumé des types et des fonctions utilisés
				
				
					
						| 
							Types et fonctions
						 | 
						
							Description
						 | 
					
					
						| 
							pthread_t 
						 | 
						
							Contexte de tâche
						 | 
					
					
						| 
							pthread_create() 
						 | 
						
							Création d'une tâche
						 | 
					
					
						| 
							pthread_join() 
						 | 
						
							Attente de fin de tâche
						 | 
					
					
						| 
							pthread_mutex_t 
						 | 
						
							Sémaphore d'exclusion mutuelle
						 | 
					
					
						| 
							pthread_mutex_lock()
						 | 
						
							Début de zone critique
						 | 
					
					
						| 
							pthread_mutex_unlock()
						 | 
						
							Fin de zone critique
						 | 
					
					
						| 
							pthread_cond_t 
						 | 
						
							Condition
						 | 
					
					
						| 
							pthread_cond_wait() 
						 | 
						
							Mise en attente
						 | 
					
					
						| 
							pthread_cond_signal()
						 | 
						
							Déclenchement
						 | 
					
				
 
				  | 
					Les essais ont été faits sous Windows 98 avec Dev-C++ et sa bibliothèque pthread. L'ordonnanceur de Windows 
					étant préemptif, les aberrations de fonctionnement sont claires et évidentes. Les essais réalisés 
					sous Linux embarqué (Kernel 2.4 pour Power PC) sont moins parlants, car sur ma configuration de 
					test, l'ordonnanceur n'est pas préemptif. Il semble aussi que, sous Windows XP, l'ordonnanceur ne 
					soit pas préemptif (time slicing), mais coopératif... A moins que j'interprète mal le fonctionnement... 
					Remarques bienvenues. 
				 | 
				
					Les remarques reçues font état des faits suivants : 
				
				
					- 
						L'ordonnanceur de GNU/Linux 2.4 est soit coopératif, soit préemptif selon une option de compilation du 
						noyau.
					
 
					- 
						L'ordonnanceur de GNU/Linux 2.6 est préemptif.
					
 
					- 
						L'ordonnanceur de MS/Windows 98 est coopératif (blocage possible par une 'boucle blanche')..
					
 
					- 
						L'ordonnanceur de MS/Windows NT/XP est préemptif (évènements + time slicing).
					
 
				
			
			IX-G. Ressources
				
				
					Pour approfondir, je recommande cet 
excellent 
					site en anglais, bien sûr. 
				
 
			
		
	
    
    
	
	
	
	
	
	
	
	
	
	
	
	
		

 
Copyright © 2008 Emmanuel Delahaye.
Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.