Planification de la synchronisation

Les programmeurs peuvent contrôler l'ordonnancement de l'exécution des unités d'exécution lorsqu'il existe des contraintes, en particulier des contraintes de temps, qui nécessitent que certaines unités d'exécution soient exécutées plus rapidement que d'autres.

Les objets de synchronisation, tels que les mutex, peuvent bloquer même les unités d'exécution à priorité élevée. Dans certains cas, un comportement indésirable, appelé inversion de priorité, peut se produire. La bibliothèque d'unités d'exécution fournit la fonction de protocoles mutex pour éviter les inversions de priorité.

La planification de la synchronisation définit la façon dont la planification de l'exécution, en particulier la priorité, d'une unité d'exécution est modifiée en maintenant un mutex. Cela permet un comportement personnalisé et évite les inversions de priorité. Il est utile lors de l'utilisation de schémas de verrouillage complexes. Certaines implémentations de la bibliothèque d'unités d'exécution ne fournissent pas de planification de synchronisation.

Inversion de priorité

L'inversion de priorité se produit lorsqu'une unité d'exécution de priorité basse contient un mutex, ce qui bloque une unité d'exécution de priorité élevée. En raison de sa faible priorité, le propriétaire du mutex peut conserver le mutex pendant une durée illimitée. Par conséquent, il devient impossible de garantir des délais d'exécution.

L'exemple suivant illustre une inversion de priorité typique. Dans cet example, on considère le cas d'un système uniprocesseur. Les inversions de priorité se produisent également sur les systèmes multiprocesseurs de la même manière.

Dans notre exemple, un mutex M est utilisé pour protéger certaines données communes. L'unité d'exécution A a un niveau de priorité de 100 et est planifiée très souvent. L'unité d'exécution B a un niveau de priorité de 20 et est une unité d'exécution d'arrière-plan. Les autres unités d'exécution du processus ont des niveaux de priorité proches de 60. Un fragment de code de l'unité d'exécution A se présente comme suit:
pthread_mutex_lock(&M);             /* 1 */
...
pthread_mutex_unlock(&M);
Un fragment de code de l'unité d'exécution B se présente comme suit:
pthread_mutex_lock(&M);          /* 2 */
...
fprintf(...);                    /* 3 */
...
pthread_mutex_unlock(&M);

Prenez en compte la chronologie d'exécution suivante. L'unité d'exécution B est planifiée et exécute la ligne 2. Lors de l'exécution de la ligne 3, l'unité d'exécution B est préemptée par l'unité d'exécution A. L'unité d'exécution A exécute la ligne 1 et est bloquée, car le mutex M est maintenu par l'unité d'exécution B. Par conséquent, d'autres unités d'exécution du processus sont planifiées. Etant donné que l'unité d'exécution B a une priorité très faible, elle ne peut pas être replanifiée sur une longue période, ce qui bloque l'unité d'exécution A, bien que l'unité d'exécution A ait une priorité très élevée.

Protocoles Mutex

Pour éviter les inversions de priorité, les protocoles mutex suivants sont fournis par la bibliothèque d'unités d'exécution:

Protocole d'héritage de priorité
Parfois appelé protocole d'héritage de priorité de base. Dans le protocole d'héritage de priorité, le détenteur de mutex hérite de la priorité de l'unité d'exécution bloquée dont la priorité est la plus élevée. Lorsqu'une unité d'exécution tente de verrouiller un processus mutex à l'aide de ce protocole et qu'il est bloqué, le propriétaire du processus mutex reçoit temporairement la priorité de l'unité d'exécution bloquée, si cette priorité est supérieure à celle du propriétaire. Il récupère sa priorité d'origine lorsqu'il déverrouille le mutex.
Protocole de protection des priorités
Parfois appelée émulation de protocole de plafond de priorité. Dans le protocole de protection de priorité, chaque mutex a un plafond de priorité. Il s'agit d'un niveau de priorité compris dans la plage de priorités valide. Lorsqu'une unité d'exécution possède un mutex, elle reçoit temporairement le plafond de priorité de mutex, si le plafond est supérieur à sa propre priorité. Il récupère sa priorité d'origine lorsqu'il déverrouille le mutex. Le plafond de priorité doit avoir la valeur de la priorité la plus élevée de toutes les unités d'exécution qui peuvent verrouiller le mutex. Sinon, des inversions de priorité ou même des interblocages peuvent se produire et le protocole serait inefficace.

Les deux protocoles augmentent la priorité d'une unité d'exécution contenant un mutex spécifique, de sorte que les échéances peuvent être garanties. En outre, lorsqu'ils sont correctement utilisés, les protocoles mutex peuvent empêcher les interblocages mutuels. Les protocoles mutex sont affectés individuellement aux mutex.

Choix d'un protocole mutex

Le choix d'un protocole mutex est effectué en définissant des attributs lors de la création d'un mutex. Le protocole mutex est contrôlé via l'attribut protocol. Cet attribut peut être défini dans l'objet d'attributs mutex à l'aide des sous-routines pthread_mutexattr_getprotocol et pthread_mutexattr_setprotocol . L'attribut protocol peut avoir l'une des valeurs suivantes:
Valeur Descriptif
PTHREAD_PRIO_DEFAULT Aucune valeur
PTHREAD_PRIO_NONE Indique qu'aucun protocole n'a été défini.
PTHREAD_PRIO_INHERIT Indique le protocole d'héritage de priorité.
PTHREAD_PRIO_PROTECT Indique le protocole de protection de priorité.
Remarque: Le comportement de PTHREAD_PRIO_DEFAULT est identique à celui de l'attribut PTHREAD_PRIO_INHERIT . En ce qui concerne le verrouillage de mutex, les unités d'exécution qui agissent avec l'attribut par défaut augmentent temporairement la priorité d'un détenteur de mutex lorsqu'un utilisateur est verrouillé et a une priorité plus élevée que le propriétaire. Par conséquent, il n'y a que trois comportements possibles, bien qu'il y ait quatre valeurs pour la priorité possible dans la structure d'attribut.

Le protocole de protection de priorité utilise un attribut supplémentaire: l'attribut prioceiling. Cet attribut contient le plafond de priorité du mutex. L'attribut prioceiling peut être contrôlé dans l'objet d'attributs mutex, à l'aide des sous-routines pthread_mutexattr_getprioceiling et pthread_mutexattr_setprioceiling .

L'attribut prioceiling d'un mutex peut également être contrôlé dynamiquement à l'aide des sous-routines pthread_mutex_getprioceiling et pthread_mutex_setprioceiling . Lors de la modification dynamique du plafond de priorité d'un mutex, le mutex est verrouillé par la bibliothèque ; il ne doit pas être conservé par l'unité d'exécution qui appelle la sous-routine pthread_mutex_setprioceiling pour éviter un interblocage. La définition dynamique du plafond de priorité d'un mutex peut être utile lors de l'augmentation de la priorité d'une unité d'exécution.

L'implémentation des protocoles mutex est facultative. Chaque protocole est une option POSIX .

Héritage ou protection

Les deux protocoles sont similaires et permettent de promouvoir la priorité de l'unité d'exécution contenant le mutex. Si les deux protocoles sont disponibles, les programmeurs doivent choisir un protocole. Le choix varie selon que les priorités des unités d'exécution qui verrouillent le mutex sont disponibles pour le programmeur qui crée le mutex. Généralement, les mutex définis par une bibliothèque et utilisés par les unités d'exécution d'application utilisent le protocole d'héritage, tandis que les mutex créés dans le programme d'application utilisent le protocole de protection.

Dans les programmes critiques pour la performance, les considérations de performance peuvent également influencer le choix. Dans la plupart des implémentations, en particulier sous AIX, la modification de la priorité d'un thread entraîne un appel au système. Par conséquent, les deux protocoles mutex diffèrent dans la quantité d'appels système qu'ils génèrent, comme suit:
  • A l'aide du protocole d'héritage, un appel système est effectué chaque fois qu'une unité d'exécution est bloquée lors de la tentative de verrouillage du mutex.
  • A l'aide du protocole de protection, un appel système est toujours effectué chaque fois que le mutex est verrouillé par une unité d'exécution.

Dans la plupart des programmes critiques pour les performances, le protocole d'héritage doit être choisi, car les exclusions mutuelles sont des objets à faible conflit. Les mutex ne sont pas conservés pendant de longues périodes ; par conséquent, il est peu probable que les unités d'exécution soient bloquées lorsqu'elles tentent de les verrouiller.