web-dev-qa-db-fra.com

Pourquoi pthread_cond_wait a-t-il de faux réveils?

Pour citer la page de manuel:

Lorsque vous utilisez des variables de condition, il existe toujours un prédicat booléen impliquant des variables partagées associées à chaque condition d'attente qui est vrai si le thread doit continuer. Des réveils parasites des fonctions pthread_cond_timedwait () ou pthread_cond_wait () peuvent se produire. Comme le retour de pthread_cond_timedwait () ou pthread_cond_wait () n'implique rien sur la valeur de ce prédicat, le prédicat doit être réévalué lors de ce retour.

Alors, pthread_cond_wait peut revenir même si vous ne l'avez pas signalé. À première vue du moins, cela semble assez atroce. Ce serait comme une fonction qui a renvoyé au hasard la mauvaise valeur ou retournée au hasard avant qu'elle n'atteigne réellement une instruction de retour appropriée. Cela semble être un bug majeur. Mais le fait qu'ils aient choisi de documenter cela dans la page de manuel plutôt que de le corriger semble indiquer qu'il existe une raison légitime pour laquelle pthread_cond_wait finit par se réveiller faussement. Vraisemblablement, il y a quelque chose d'intrinsèque dans la façon dont cela fonctionne qui fait que cela ne peut pas être aidé. La question est quoi.

Pourquoi fait pthread_cond_wait rentrer faux? Pourquoi ne peut-il pas garantir qu'il ne se réveillera que lorsqu'il aura été correctement signalé? Quelqu'un peut-il expliquer la raison de son comportement fallacieux?

131
Jonathan M Davis

L'explication suivante est donnée par David R. Butenhof dans "Programmation avec des threads POSIX" (p. 80):

Des réveils parasites peuvent sembler étranges, mais sur certains systèmes multiprocesseurs, rendre le réveil des conditions complètement prévisible peut ralentir considérablement toutes les opérations de variables de conditions.

Dans ce qui suit discussion comp.programming.threads , il développe la réflexion derrière la conception:

 Patrick Doyle a écrit: 
> Dans l'article, Tom Payne a écrit: 
>> Kaz Kylheku a écrit: 
>>: C'est parce que les implémentations ne peuvent parfois pas éviter insérer 
>>: ces faux réveils; il pourrait être coûteux de les empêcher. 
 
>> Mais pourquoi? Pourquoi est-ce si difficile? Par exemple, parlons-nous de 
>> Situations où une attente expire au moment où un signal arrive? 
 
> Vous savez, je me demande si les concepteurs de pthreads ont utilisé une logique comme celle-ci: 
> Les utilisateurs des variables de condition doivent quand même vérifier la condition à la sortie, 
 > nous ne leur imposerons donc aucun fardeau supplémentaire si nous autorisons 
> réveils parasites; et comme il est concevable que l’autorisation de 
> réveils parasites puisse accélérer la mise en œuvre, cela ne peut aider que si nous 
> les autorisons. 
 
> Ils n'avaient peut-être pas en tête d'implémentation particulière. 
 
 Vous n'êtes en fait pas loin du tout, sauf que vous n'avez pas poussé assez loin. 
 
 L'intention était de forcer le code correct/robuste en exigeant des boucles de prédicat. Ceci était 
 Motivé par le contingent académique prouvablement correct parmi les "sujets centraux" du 
 Groupe de travail, bien que je ne pense pas que quiconque soit vraiment en désaccord avec l'intention 
 Une fois qu'ils ont compris ce que cela signifiait. 
 
 Nous avons suivi cette intention avec plusieurs niveaux de justification. Le premier était que 
 "Religieusement" en utilisant une boucle protège l'application contre ses propres pratiques de codage 
 Imparfaites. La seconde était qu'il n'était pas difficile d'imaginer de manière abstraite 
 Machines et code d'implémentation qui pourraient exploiter cette exigence pour améliorer 
 La performance des opérations d'attente de condition moyenne en optimisant les mécanismes de synchronisation 
 . 
/------------------ [David.Buten ... @ compaq.com] -------------- ----\
 | Compaq Computer Corporation Architecte de thread POSIX | 
 | Mon livre: http://www.awl.com/cseng/titles/0-201-63392-2/ | 
\----- [http://home.earthlink.net/~anneart/family/dave.html] -----/
 
73
NPE

Il y a au moins deux choses qu'un "réveil fallacieux" pourrait signifier:

  • Un thread bloqué dans pthread_cond_wait peut revenir de l'appel même si aucun appel n'a été signalé ou diffusé à la condition.
  • Un thread bloqué dans pthread_cond_wait renvoie en raison d'un appel à signaler ou à diffuser, mais après avoir acquis de nouveau le mutex, le prédicat sous-jacent n'est plus vrai.

Mais le dernier cas peut se produire même si l'implémentation de la variable de condition ne permet pas le premier cas. Prenons une file d'attente de consommateurs producteurs et trois threads.

  • Le thread 1 vient de retirer un élément de la file d'attente et de libérer le mutex, et la file d'attente est maintenant vide. Le thread fait tout ce qu'il fait avec l'élément qu'il a acquis sur certains CPU.
  • Le thread 2 tente de retirer un élément de la file d'attente, mais trouve la file d'attente vide lorsqu'il est vérifié sous le mutex, appelle pthread_cond_wait et bloque l'appel en attente de signal/diffusion.
  • Le thread 3 obtient le mutex, insère un nouvel élément dans la file d'attente, notifie la variable de condition et libère le verrou.
  • En réponse à la notification du thread 3, le thread 2, qui attendait la condition, doit s'exécuter.
  • Cependant, avant que le thread 2 ne parvienne à accéder au CPU et à saisir le verrou de file d'attente, le thread 1 termine sa tâche actuelle et revient à la file d'attente pour plus de travail. Il obtient le verrouillage de la file d'attente, vérifie le prédicat et constate qu'il y a du travail dans la file d'attente. Il procède à la mise en file d'attente de l'élément inséré par le thread 3, libère le verrou et fait ce qu'il fait avec l'élément que le thread 3 a mis en file d'attente.
  • Le thread 2 obtient désormais sur un processeur et obtient le verrou, mais lorsqu'il vérifie le prédicat, il constate que la file d'attente est vide. Thread 1 'a volé' l'objet, donc le réveil semble être faux. Le thread 2 doit de nouveau attendre la condition.

Donc, comme vous devez déjà toujours vérifier le prédicat sous une boucle, cela ne fait aucune différence si les variables de condition sous-jacentes peuvent avoir d'autres sortes de réveils parasites.

95
acm

La section "Réveils multiples par signal de condition" dans pthread_cond_signal a un exemple d'implémentation de pthread_cond_wait et pthread_cond_signal qui implique des réveils parasites.

7
Jingguo Yao