50 votes

Pourquoi une boucle while est-elle nécessaire autour des conditions d'attente du pthread ?

Je suis en train d'apprendre le pthread et les conditions d'attente. D'après ce que je sais, un fil d'attente typique ressemble à ceci :

pthread_mutex_lock(&m);
while(!condition)
     pthread_cond_wait(&cond, &m);
// Thread stuff here
pthread_mutex_unlock(&m);

Ce que je ne comprends pas, c'est pourquoi la ligne while(!condition) est nécessaire même si j'utilise pthread_cond_signal() pour réveiller le fil.

Je peux comprendre que si j'utilise pthread_cond_broadcast() J'ai besoin de tester la condition, parce que je me réveille tous threads en attente et l'un d'entre eux peut rendre la condition fausse à nouveau avant de déverrouiller le mutex (et ainsi transférer l'exécution à un autre thread réveillé qui ne devrait pas s'exécuter à ce moment-là). Mais si j'utilise pthread_cond_signal() Je me réveille juste un donc la condition doit être vraie. Le code pourrait donc ressembler à ceci :

pthread_mutex_lock(&m);
pthread_cond_wait(&cond, &m);
// Thread stuff here
pthread_mutex_unlock(&m);

J'ai lu quelque chose sur les signaux parasites qui peuvent se produire. Est-ce que c'est (et seulement cela) la raison ? Pourquoi devrais-je avoir des signaux parasites ? Ou y a-t-il autre chose que je ne comprends pas ?

Je suppose que le code du signal est comme ça :

pthread_mutex_lock(&m);
condition = true;
pthread_cond_signal(&cond); // Should wake up *one* thread
pthread_mutex_unlock(&m);

48voto

Chris Lu Points 211

La vraie raison pour laquelle vous devriez mettre pthread_cond_wait dans une boucle while n'est pas à cause du réveil intempestif. Même si votre variable de condition n'avait pas de réveil intempestif, vous auriez toujours besoin de la boucle pour attraper un type d'erreur commun. Pourquoi ? Considérez ce qui peut arriver si plusieurs threads attendent la même condition :

Thread 1                         Thread 2           Thread 3
check condition (fails)
(in cond_wait) unlock mutex
(in cond_wait) wait
                                 lock mutex
                                 set condition
                                 signal condvar
                                 unlock mutex
                                                    lock mutex
                                                    check condition (succeeds)
                                                    do stuff
                                                    unset condition
                                                    unlock mutex
(in cond_wait) wake up
(in cond_wait) lock mutex
<thread is awake, but condition
is unset>

Le problème ici est que le thread doit libérer le mutex avant d'attendre, ce qui permet potentiellement à un autre thread de "voler" ce que ce thread attendait. À moins qu'il ne soit garanti qu'un seul thread puisse attendre sur cette condition, il est incorrect de supposer que la condition est valide lorsqu'un thread se réveille.

1 votes

Exactement. J'ai voté pour. Cela devrait recevoir plus d'attention que la réponse acceptée.

30voto

nos Points 102226

Oui, l'API de pthread permet des réveils intempestifs, et c'est la seule raison à laquelle je pense qui pourrait entraîner la nécessité de revérifier la condition lors de l'utilisation de pthread_cond_signal.

Peut-être qu'une version particulière sur une plate-forme particulière ne provoquera jamais de réveils intempestifs, mais lorsque la documentation indique que vous devez revérifier la condition, je le ferais.

Voici une façon de provoquer des réveils intempestifs sous linux (extrait de wikipedia ) :

Réveil intempestif sous Linux

La fonction pthread_cond_wait() sous Linux est implémentée en utilisant l'appel système futex. Chaque appel système bloquant sous Linux retourne brusquement avec EINTR lorsque le processus reçoit un signal. Un signal POSIX génère donc un réveil intempestif. Cet état n'est pas trivial à corriger pour 2 raisons :

  • Faire en sorte que la distribution des signaux n'interrompe pas les appels système permet de garder la pile utilisée. Si un autre appel système est invoqué pendant une routine de traitement des signaux en espace utilisateur, et que cet appel système est également interrompu, etc., la pile du noyau pourrait s'épuiser rapidement. Retourner avec EINTR permet de garder l'utilisation de la pile sous contrôle. La glibc vérifie (ou est supposée vérifier) pour EINTR après chaque appel système bloquant. La structure de données futex contient suffisamment d'informations pour relancer ces appels.
  • pthread_cond_wait() ne peut pas relancer l'attente car il peut manquer un vrai réveil pendant le peu de temps où il était en dehors de l'appel système futex. Cette condition de course ne peut être évitée que par la vérification d'un invariant par l'appelant. Pour compliquer encore les choses, la spécification POSIX stipule que "Ces fonctions ne renverront pas un code d'erreur de EINTR" réf. Ces fonctions renvoient zéro dans le cas d'un réveil fallacieux ref, selon POSIX.

En contradiction avec POSIX, la page de manuel de LinuxThreads ("old" linux threads) dit que EINTR est retourné lorsque "pthread_cond_timedwait a été interrompu par un signal". La version récente (NPTL) de pthread_cond_timedwait semble toutefois respecter le comportement POSIX (retour de zéro en cas de réveil intempestif).

17voto

Steve Jessop Points 166970

Supposons que vous ne vérifiez pas la condition. Alors, en général, vous ne pouvez pas éviter que la mauvaise chose suivante se produise (du moins, vous ne pouvez pas l'éviter en une seule ligne de code) :

 Sender                             Receiver
locks mutex
sets condition
signals condvar, but nothing 
  is waiting so has no effect
releases mutex
                                    locks mutex
                                    waits. Forever.

Bien sûr, votre deuxième exemple de code pourrait éviter cela en faisant :

pthread_mutex_lock(&m);
if (!condition) pthread_cond_wait(&cond, &m);
// Thread stuff here
pthread_mutex_unlock(&m);

Alors il est certain que s'il n'y a jamais qu'un seul récepteur, et si cond_signal était la seule chose qui pouvait le réveiller, alors il ne se réveillerait que lorsque la condition est remplie et n'aurait donc pas besoin d'une boucle. Nous ne comprenons pas pourquoi le deuxième "si" n'est pas vrai.

0 votes

Je vois, donc un "si" est nécessaire pour une raison logique (attente sans fin), mais un "while" est en fait nécessaire pour des raisons de mise en œuvre (signaux parasites).

0 votes

Oui, quand j'ai utilisé la bibliothèque pthreads pour la première fois, je me suis posé la même question. J'ai omis de vérifier une variable d'état et mon programme signalait avant que l'attente ne se produise. C'est tout l'intérêt des fonctions wait/signal. Pour attendre et signaler un changement d'état de la mémoire protégé par un mutex.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X