Je suis nouveau dans le multithreading et j'essayais de comprendre le fonctionnement des mutex. J'ai fait beaucoup de recherches sur Google, mais cela a laissé des doutes sur son fonctionnement, car j'ai créé mon propre programme dans lequel le verrouillage ne fonctionnait pas.
Une syntaxe absolument non intuitive du mutex est pthread_mutex_lock( &mutex1 );
, où il semble que le mutex est verrouillé, lorsque ce que je veux vraiment verrouiller est une autre variable. Cette syntaxe signifie-t-elle que le verrouillage d'un mutex verrouille une région de code jusqu'à ce que le mutex soit déverrouillé? Alors, comment les threads savent-ils que la région est verrouillée? [ MISE À JOUR: les threads savent que la région est verrouillée, par la séparation de la mémoire ]. Et un tel phénomène n'est-il pas censé s'appeler section critique? [ MISE À JOUR: les objets de section critique sont disponibles uniquement sous Windows, où les objets sont plus rapides que les mutex et ne sont visibles que par le thread qui les implémente. Sinon, la section critique fait simplement référence à la zone de code protégée par un mutex ]
En bref, pourriez-vous s'il vous plaît aider avec le programme d'exemple de mutex le plus simple possible et l' explication la plus simple possible sur la logique de son fonctionnement? Je suis sûr que cela aidera beaucoup d'autres débutants.
Réponses:
Voici mon humble tentative d'expliquer le concept aux débutants du monde entier: (une version à code couleur sur mon blog également)
Beaucoup de gens courent vers une cabine téléphonique isolée (ils n'ont pas de téléphone portable) pour parler à leurs proches. La première personne à attraper la poignée de la porte de la cabine est celle qui est autorisée à utiliser le téléphone. Il doit continuer à s'accrocher à la poignée de la porte tant qu'il utilise le téléphone, sinon quelqu'un d'autre saisira la poignée, le jettera dehors et parlera à sa femme :) Il n'y a pas de système de file d'attente en tant que tel. Lorsque la personne a terminé son appel, sort de la cabine et quitte la poignée de la porte, la prochaine personne à saisir la poignée de la porte sera autorisée à utiliser le téléphone.
Un fil est: Chaque personne
Le mutex est: La poignée de la porte
La serrure est: La main de la personne
La ressource est: Le téléphone
Tout thread qui doit exécuter des lignes de code qui ne doivent pas être modifiées par d'autres threads en même temps (en utilisant le téléphone pour parler à sa femme), doit d'abord acquérir un verrou sur un mutex (serrant la poignée de la porte de la cabine. ). Ce n'est qu'alors qu'un thread pourra exécuter ces lignes de code (en passant l'appel téléphonique).
Une fois que le thread a exécuté ce code, il doit libérer le verrou sur le mutex afin qu'un autre thread puisse acquérir un verrou sur le mutex (d'autres personnes pouvant accéder à la cabine téléphonique).
[ Le concept d'avoir un mutex est un peu absurde quand on considère l'accès exclusif au monde réel, mais dans le monde de la programmation, je suppose qu'il n'y avait pas d'autre moyen de laisser les autres threads «voir» qu'un thread exécutait déjà certaines lignes de code. Il existe des concepts de mutex récursifs, etc., mais cet exemple était uniquement destiné à vous montrer le concept de base. J'espère que l'exemple vous donne une image claire du concept. ]
Avec le threading C ++ 11:
Compilez et exécutez avec
g++ -std=c++0x -pthread -o thread thread.cpp;./thread
Au lieu d'utiliser explicitement
lock
etunlock
, vous pouvez utiliser des crochets comme indiqué ici , si vous utilisez un verrou à portée pour l'avantage qu'il offre . Les verrous à portée ont cependant une légère surcharge de performances.la source
(could've shown scoped locking by not using acquire and release - which also is exception safe -, but this is clearer
. Quant à l'utilisation du verrouillage de portée, c'est au développeur, en fonction du type d'application qu'il crée. Cette réponse visait à aborder la compréhension de base du concept de mutex et non à entrer dans toutes les complexités de celui-ci, donc vos commentaires et liens sont les bienvenus, mais un peu hors de la portée de ce tutoriel.Bien qu'un mutex puisse être utilisé pour résoudre d'autres problèmes, la principale raison pour laquelle ils existent est de fournir une exclusion mutuelle et de résoudre ainsi ce que l'on appelle une condition de race. Lorsque deux (ou plus) threads ou processus tentent d'accéder à la même variable simultanément, nous avons un potentiel pour une condition de concurrence. Considérez le code suivant
Les éléments internes de cette fonction semblent si simples. Ce n'est qu'une seule déclaration. Cependant, un équivalent en langage pseudo-assembleur typique pourrait être:
Étant donné que les instructions équivalentes en langage assembleur sont toutes nécessaires pour effectuer l'opération d'incrémentation sur i, nous disons que l'incrémentation de i est une opération non atmosphérique. Une opération atomique est une opération qui peut être effectuée sur le matériel avec une garantie de ne pas être interrompue une fois que l'exécution de l'instruction a commencé. L'incrémentation de i consiste en une chaîne de 3 instructions atomiques. Dans un système simultané où plusieurs threads appellent la fonction, des problèmes surviennent lorsqu'un thread lit ou écrit au mauvais moment. Imaginez que nous ayons deux threads exécutés simultanément et que l'un appelle la fonction immédiatement après l'autre. Disons aussi que nous avons initialisé i à 0. Supposons également que nous avons beaucoup de registres et que les deux threads utilisent des registres complètement différents, il n'y aura donc pas de collisions. Le moment réel de ces événements peut être:
Ce qui s'est passé, c'est que nous avons deux threads incrémentant i simultanément, notre fonction est appelée deux fois, mais le résultat est incompatible avec ce fait. Il semble que la fonction n'a été appelée qu'une seule fois. C'est parce que l'atomicité est "cassée" au niveau de la machine, ce qui signifie que les threads peuvent s'interrompre ou travailler ensemble aux mauvais moments.
Nous avons besoin d'un mécanisme pour résoudre ce problème. Nous devons imposer un ordre aux instructions ci-dessus. Un mécanisme courant consiste à bloquer tous les threads sauf un. Pthread mutex utilise ce mécanisme.
Tout thread qui doit exécuter des lignes de code qui peuvent modifier de manière non sécurisée les valeurs partagées par d'autres threads en même temps (en utilisant le téléphone pour parler à sa femme), doit d'abord être obligé d'acquérir un verrou sur un mutex. De cette façon, tout thread qui nécessite l'accès aux données partagées doit passer par le verrou mutex. Ce n'est qu'alors qu'un thread pourra exécuter le code. Cette section de code est appelée une section critique.
Une fois que le thread a exécuté la section critique, il doit libérer le verrou sur le mutex afin qu'un autre thread puisse acquérir un verrou sur le mutex.
Le concept d'avoir un mutex semble un peu étrange lorsque l'on considère les humains cherchant un accès exclusif à des objets réels et physiques, mais lors de la programmation, nous devons être intentionnels. Les fils et processus simultanés n'ont pas l'éducation sociale et culturelle que nous faisons, nous devons donc les forcer à partager les données correctement.
Alors techniquement, comment fonctionne un mutex? Ne souffre-t-il pas des mêmes conditions de course que nous avons évoquées plus tôt? Pthread_mutex_lock () n'est-il pas un peu plus complexe qu'un simple incrément d'une variable?
Techniquement parlant, nous avons besoin d'un support matériel pour nous aider. Les concepteurs de matériel nous donnent des instructions machine qui font plus d'une chose mais sont garanties atomiques. Un exemple classique d'une telle instruction est le test-and-set (TAS). Lorsque vous essayez d'acquérir un verrou sur une ressource, nous pouvons utiliser le TAS pour vérifier si une valeur en mémoire est 0. Si c'est le cas, ce serait notre signal que la ressource est en cours d'utilisation et que nous ne faisons rien (ou plus précisément , nous attendons par un mécanisme. Un mutex pthreads nous mettra dans une file d'attente spéciale dans le système d'exploitation et nous avertira lorsque la ressource sera disponible. Les systèmes plus stupides peuvent nous obliger à faire une boucle de rotation serrée, en testant la condition encore et encore) . Si la valeur en mémoire n'est pas 0, le TAS définit l'emplacement sur autre chose que 0 sans utiliser d'autres instructions. Il' C'est comme combiner deux instructions d'assemblage en 1 pour nous donner l'atomicité. Ainsi, le test et la modification de la valeur (si la modification est appropriée) ne peuvent pas être interrompus une fois qu'ils ont commencé. Nous pouvons construire des mutex en plus d'une telle instruction.
Remarque: certaines sections peuvent ressembler à une réponse précédente. J'ai accepté son invitation à éditer, il a préféré la manière originale, donc je garde ce que j'avais qui est imprégné d'un peu de son verbiage.
la source
Le meilleur tutoriel sur les threads que je connaisse est ici:
https://computing.llnl.gov/tutorials/pthreads/
J'aime le fait qu'il soit écrit sur l'API, plutôt que sur une implémentation particulière, et cela donne de beaux exemples simples pour vous aider à comprendre la synchronisation.
la source
Je suis tombé sur ce post récemment et je pense qu'il a besoin d'une solution mise à jour pour le mutex c ++ 11 de la bibliothèque standard (à savoir std :: mutex).
J'ai collé du code ci-dessous (mes premiers pas avec un mutex - j'ai appris la concurrence sur win32 avec HANDLE, SetEvent, WaitForMultipleObjects, etc.).
Puisque c'est ma première tentative avec std :: mutex et ses amis, j'aimerais voir les commentaires, suggestions et améliorations!
la source
La fonction
pthread_mutex_lock()
soit acquiert le mutex pour le thread appelant ou bloque le fil jusqu'à ce que le mutex peut être acquise. Lespthread_mutex_unlock()
versions connexes du mutex.Considérez le mutex comme une file d'attente; chaque thread qui tente d'acquérir le mutex sera placé à la fin de la file d'attente. Lorsqu'un thread libère le mutex, le thread suivant de la file d'attente se désactive et est maintenant en cours d'exécution.
Une section critique fait référence à une région de code où le non-déterminisme est possible. Cela est souvent dû au fait que plusieurs threads tentent d'accéder à une variable partagée. La section critique n'est pas sûre tant qu'une sorte de synchronisation n'est pas en place. Un verrou mutex est une forme de synchronisation.
la source
Vous êtes censé vérifier la variable mutex avant d'utiliser la zone protégée par le mutex. Ainsi, votre pthread_mutex_lock () pourrait (selon l'implémentation) attendre que mutex1 soit libéré ou renvoyer une valeur indiquant que le verrou ne pourrait pas être obtenu si quelqu'un d'autre l'a déjà verrouillé.
Mutex n'est en réalité qu'un sémaphore simplifié. Si vous lisez à leur sujet et que vous les comprenez, vous comprenez les mutex. Il y a plusieurs questions concernant les mutex et les sémaphores dans SO. Différence entre sémaphore binaire et mutex , quand devrions-nous utiliser mutex et quand devrions-nous utiliser sémaphore et ainsi de suite. L'exemple des toilettes dans le premier lien est à peu près aussi bon que l'on puisse penser. Tout ce que le code fait est de vérifier si la clé est disponible et si c'est le cas, de la réserver. Notez que vous ne réservez pas vraiment les toilettes elles-mêmes, mais la clé.
la source
pthread_mutex_lock
ne peut pas revenir si quelqu'un d'autre tient la serrure. Cela bloque dans ce cas et c'est tout le problème.pthread_mutex_trylock
est la fonction qui reviendra si le verrou est maintenu.Pour ceux qui recherchent l'exemple du mutex shortex:
la source
EXEMPLE DE SEMAPHORE:
Référence: http://pages.cs.wisc.edu/~remzi/Classes/537/Fall2008/Notes/threads-semaphores.txt
la source