Quelles sont les différences fondamentales entre un sémaphore et un spin-lock?
Quand utiliserions-nous un sémaphore sur un verrou tournant?
Spinlock et sémaphore diffèrent principalement sur quatre points:
1. Ce qu'ils sont
Un spinlock est une implémentation possible d'un verrou, à savoir celui qui est implémenté par une attente occupée ("spinning"). Un sémaphore est une généralisation d'un verrou (ou, à l'inverse, un verrou est un cas particulier d'un sémaphore). Habituellement, mais pas nécessairement , les verrous tournants ne sont valables que dans un seul processus tandis que les sémaphores peuvent également être utilisés pour synchroniser entre différents processus.
Un verrou fonctionne pour l'exclusion mutuelle, c'est-à-dire que un thread à la fois peut acquérir le verrou et procéder à une "section critique" de code. Habituellement, cela signifie du code qui modifie certaines données partagées par plusieurs threads.
Un sémaphore a un compteur et se permettra d'être acquis par un ou plusieurs threads, selon sur la valeur que vous lui publiez et (dans certaines implémentations) en fonction de sa valeur maximale autorisée.
Dans la mesure où l'on peut considérer un verrou comme un cas particulier de sémaphore d'une valeur maximale de 1.
2. Ce qu'ils font
Comme indiqué ci-dessus, un verrou tournant est un verrou, et donc un mécanisme d'exclusion mutuelle (strictement 1 à 1). Il fonctionne en interrogeant et/ou modifiant à plusieurs reprises un emplacement de mémoire, généralement de manière atomique. Cela signifie que l'acquisition d'un verrou tournant est une opération "occupée" qui peut graver des cycles de processeur pendant une longue période (peut-être pour toujours!) Alors qu'elle n'obtient effectivement "rien".
La principale incitation à une telle approche est le fait qu'un changement de contexte a une surcharge équivalente à une rotation de quelques centaines (ou peut-être mille) fois, donc si un verrou peut être acquis en brûlant quelques cycles de rotation, cette peut globalement très bien être plus efficace. En outre, pour les applications en temps réel, il peut ne pas être acceptable de bloquer et d'attendre que l'ordonnanceur leur revienne à un moment lointain dans le futur.
Un sémaphore, en revanche, ne tourne pas du tout, ou ne tourne que pendant très peu de temps (comme une optimisation pour éviter la surcharge de l'appel système). Si un sémaphore ne peut pas être acquis, il se bloque, abandonnant le temps CPU à un autre thread prêt à fonctionner. Cela peut bien sûr signifier que quelques millisecondes s'écoulent avant que votre thread ne soit à nouveau planifié, mais si ce n'est pas un problème (généralement ce n'est pas le cas), cela peut être une approche très efficace et conservatrice du CPU.
3. Comment ils se comportent en présence de congestion
C'est une idée fausse que les verrous tournants ou les algorithmes sans verrouillage sont "généralement plus rapides", ou qu'ils ne sont utiles que pour des "tâches très courtes" (idéalement, aucun objet de synchronisation ne devrait être conservé plus longtemps que nécessaire, déjà).
La seule différence importante est le comportement des différentes approches en présence de congestion .
Un système bien conçu a normalement peu ou pas de congestion (cela signifie que tous les threads n'essaient pas d'acquérir le verrou en même temps). Par exemple, on ne devrait normalement pas écrire du code qui acquiert un verrou, puis charger un demi-mégaoctet de données compressées Zip à partir du réseau, décoder et analyser le données, et enfin modifie une référence partagée (ajouter des données à un conteneur, etc.) avant de libérer le verrou. Au lieu de cela, on n'acquerrait le verrou que dans le but d'accéder à la ressource partagée .
Comme cela signifie qu'il y a beaucoup plus de travail à l'extérieur de la section critique qu'à l'intérieur, la probabilité qu'un filetage se trouve à l'intérieur de la section critique est relativement faible, et donc peu de fils rivalisent pour le verrou en même temps . Bien sûr, de temps en temps, deux threads tentent d'acquérir le verrou en même temps (si cela ne pouvait pas se produire, vous n'auriez pas besoin d'un verrou !), mais c'est plutôt l'exception que la règle dans un système "sain".
Dans un tel cas, un spinlock surpasse considérablement un sémaphore car s'il n'y a pas de congestion de verrouillage, la surcharge d'acquisition du spinlock n'est que d'une douzaine de cycles par rapport à à des centaines/milliers de cycles pour un changement de contexte ou 10 à 20 millions de cycles pour perdre le reste d'une tranche de temps.
D'un autre côté, étant donné la congestion élevée ou si le verrouillage est maintenu pendant de longues périodes (parfois vous ne pouvez tout simplement pas vous en empêcher!), Un verrou tournant brûlera des quantités folles de cycles CPU pour ne rien faire.
Un sémaphore (ou mutex) est un bien meilleur choix dans ce cas, car il permet à un autre thread d'exécuter des tâches utiles pendant cette temps. Ou, si aucun autre thread n'a quelque chose d'utile à faire, cela permet au système d'exploitation d'étrangler le CPU et de réduire la chaleur/économiser l'énergie.
De plus, sur un système à cœur unique, un verrou tournant sera assez inefficace en présence d'encombrement de verrou, car un filetage en rotation perdra son temps complet à attendre un changement d'état qui ne peut pas se produire (pas jusqu'à ce que le fil de libération soit planifié, ce qui ne se produit pas pendant l'exécution du thread en attente!). Par conséquent, étant donné toute quantité de conflits, l'acquisition du verrou prend environ 1 1/2 tranches de temps dans le meilleur des cas (en supposant que le thread de libération est le suivant en cours de programmation), ce qui n'est pas un très bon comportement.
4. Comment ils sont mis en œuvre
De nos jours, un sémaphore est généralement enveloppé sys_futex
sous Linux (éventuellement avec un spinlock qui se ferme après quelques tentatives).
Un verrou tournant est généralement implémenté à l'aide d'opérations atomiques, et sans utiliser quoi que ce soit fourni par le système d'exploitation. Dans le passé, cela signifiait utiliser les instructions intrinsèques du compilateur ou des instructions d'assembleur non portables. Pendant ce temps, C++ 11 et C11 ont tous deux des opérations atomiques dans le langage, donc en dehors de la difficulté générale d'écrire du code sans verrou prouvablement correct, il est maintenant possible d'implémenter du code sans verrou dans un système entièrement portable et (presque) manière indolore.
très simplement, un sémaphore est un objet de synchronisation "cédant", un spinlock est un "busywait". (Il y a un peu plus de sémaphores en ce sens qu'ils synchronisent plusieurs threads, contrairement à un mutex ou un garde ou un moniteur ou une section critique qui protège une région de code d'un seul thread)
Vous utiliseriez un sémaphore dans plus de circonstances, mais utilisez un verrou tournant où vous allez verrouiller pendant très peu de temps - il y a un coût à verrouiller surtout si vous verrouillez beaucoup. Dans de tels cas, il peut être plus efficace de verrouiller pendant un petit moment en attendant que la ressource protégée soit déverrouillée. Évidemment, il y a un impact sur les performances si vous tournez trop longtemps.
généralement, si vous tournez plus longtemps qu'un quantum de thread, vous devez utiliser un sémaphore.
Au-delà de ce que Yoav Aviram et gbjbaanb ont dit, l'autre point clé était que vous n'utiliseriez jamais de verrou rotatif sur une machine à processeur unique, alors qu'un sémaphore aurait du sens sur une telle machine. De nos jours, vous avez souvent du mal à trouver une machine sans plusieurs cœurs, ou hyperthreading, ou équivalent, mais dans les circonstances où vous n'avez qu'un seul processeur, vous devez utiliser des sémaphores. (J'espère que la raison est évidente. Si le processeur unique est occupé à attendre que quelque chose d'autre libère le verrou de rotation, mais qu'il s'exécute sur le seul processeur, il est peu probable que le verrou soit libéré jusqu'à ce que le processus ou le thread en cours soit préempté par l'O/S, ce qui peut prendre un certain temps et rien d'utile ne se produit jusqu'à ce que la préemption se produise.)
Depuis les pilotes de périphériques Linux par Rubinni
Contrairement aux sémaphores, les verrous tournants peuvent être utilisés dans du code qui ne peut pas dormir, comme les gestionnaires d'interruption
Je ne suis pas un expert du noyau mais voici quelques points:
Même une machine monoprocesseur peut utiliser des verrous tournants si la préemption du noyau est activée lors de la compilation du noyau. Si la préemption du noyau est désactivée, alors spin-lock (peut-être) se développe en une instruction void.
De plus, lorsque nous essayons de comparer Semaphore vs Spin-lock, je pense que le sémaphore fait référence à celui utilisé dans le noyau - PAS celui utilisé pour IPC (userland).
Fondamentalement, le spin-lock doit être utilisé si la section critique est petite (plus petite que la surcharge de sommeil/réveil) et que la section critique n'appelle rien qui puisse dormir! Un sémaphore doit être utilisé si la section critique est plus grande et peut dormir.
Raman Chalotra.
Spinlock fait référence à une implémentation du verrouillage inter-thread utilisant des instructions d'assemblage dépendant de la machine (telles que test-and-set). Il est appelé spinlock car le thread attend simplement dans une boucle ("spins") vérifiant à plusieurs reprises jusqu'à ce que le verrou soit disponible (attente occupée). Les verrous tournants sont utilisés comme substitut aux mutex, qui sont une fonctionnalité fournie par les systèmes d'exploitation (pas le CPU), car les verrous tournants fonctionnent mieux s'ils sont verrouillés pendant une courte période.
Une sémaphore est une installation fournie par les systèmes d'exploitation pour IPC, son objectif principal est donc la communication inter-processus. Étant une installation fournie par le système d'exploitation, ses performances ne seront pas aussi bonnes que celles d'un verrou tournant pour le verrouillage entre les têtes (bien que possible). Les sémaphores sont mieux adaptés au verrouillage pendant de plus longues périodes.
Cela dit, la mise en œuvre de splinlocks dans Assembly est délicate et non portable.
Je voudrais ajouter mes observations, plus générales et peu spécifiques à Linux.
Selon l'architecture de la mémoire et les capacités du processeur, vous pourriez avoir besoin d'un verrou rotatif pour implémenter un sémaphore sur un système multicœur ou multiprocesseur, car dans de tels systèmes, une condition de concurrence peut se produire lorsque deux threads/processus ou plus le souhaitent d'acquérir un sémaphore.
Oui, si votre architecture mémoire offre le verrouillage d'une section mémoire par un cœur/processeur retardant tous les autres accès, et si vos processeurs proposent un test-and-set, vous pouvez implémenter un sémaphore sans spin-lock (mais très soigneusement! ).
Cependant, comme des systèmes multicœurs simples/bon marché sont conçus (je travaille dans des systèmes embarqués), toutes les architectures de mémoire ne prennent pas en charge ces fonctionnalités multicœurs/multiprocesseurs, uniquement test et configuration ou équivalent. Une implémentation pourrait alors être la suivante:
La libération du sémaphore devrait être implémentée comme suit:
Oui, et pour de simples sémaphores binaires au niveau du système d'exploitation, il serait possible d'utiliser uniquement un verrou rotatif en remplacement. Mais seulement si les sections de code à protéger sont vraiment très petites.
Comme indiqué précédemment, si et quand vous implémentez votre propre système d'exploitation, assurez-vous d'être prudent. Déboguer de telles erreurs est amusant (à mon avis, pas partagé par beaucoup), mais surtout très fastidieux et difficile.
Spinlock est utilisé si et seulement si vous êtes à peu près certain que le résultat attendu se produira très peu de temps, avant l'expiration du temps de tranche d'exécution de votre thread.
Exemple: dans le module de pilote de périphérique, le pilote écrit "0" dans le registre matériel R0 et maintenant il doit attendre que ce registre R0 devienne 1. Le H/W lit le R0 et fait un peu de travail et écrit "1" dans R0. C'est généralement rapide (en micro secondes). Maintenant, la rotation est bien meilleure que d'aller dormir et interrompue par le H/W. Bien sûr, pendant la rotation, la condition de panne H/W doit être prise en compte!
Il n'y a absolument aucune raison pour qu'une application utilisateur tourne. Ça n'a pas de sens. Vous allez tourner pour qu'un événement se produise et cet événement doit être complété par une autre application de niveau utilisateur qui n'est jamais garantie de se produire dans le délai rapide. Donc, je ne tournerai pas du tout en mode utilisateur. Je ferais mieux de dormir () ou mutexlock () ou sémaphore lock () en mode utilisateur.
Un "mutex" (ou "verrou d'exclusion mutuelle") est un signal que deux ou plusieurs processus asynchrones peuvent utiliser pour réserver une ressource partagée à un usage exclusif. Le premier processus qui obtient la propriété du "mutex" obtient également la propriété de la ressource partagée. D'autres processus doivent attendre que le premier processus libère sa propriété du "mutex" avant de tenter de l'obtenir.
La primitive de verrouillage la plus courante dans le noyau est le spinlock. Le spinlock est un verrou simple simple. Si un processus tente d'acquérir un verrou tournant et qu'il n'est pas disponible, le processus continuera d'essayer (tourner) jusqu'à ce qu'il puisse acquérir le verrou. Cette simplicité crée un petit verrou rapide.
De quelle est la différence entre les verrous tournants et les sémaphores? par Maciej Piechotka :
Les deux gèrent une ressource limitée. Je vais d'abord décrire la différence entre le sémaphore binaire (mutex) et le verrouillage de spin.
Spin locks effectuer une attente occupée - c'est-à-dire qu'il continue de fonctionner en boucle:
while (try_acquire_resource ()); ... Libération();Il effectue un verrouillage/déverrouillage très léger, mais si le thread de verrouillage sera préempté par d'autres qui tenteront d'accéder aux mêmes ressources, le second essaiera simplement d'acquitter la ressource jusqu'à ce qu'elle soit épuisée. Quanta CPU.
D'un autre côté, les mutex se comportent plutôt comme:if (! try_lock ()) { add_to_waiting_queue (); wait (); } ... process * p = get_next_process_from_waiting_queue (); p-> wakeUp ();Par conséquent, si le thread tente d'acquérir une ressource bloquée, il sera suspendu jusqu'à ce qu'il soit disponible. Le verrouillage/déverrouillage est beaucoup plus lourd mais l'attente est "libre" et "équitable".
Semaphore est un verrou qui peut être utilisé plusieurs fois (connu par l'initialisation) plusieurs fois - par exemple, 3 threads sont autorisés à contenir simultanément la ressource mais pas plus. Il est utilisé par exemple en cas de problème producteur/consommateur ou en général dans les files d'attente:
P (resources_sem) Resource = resources.pop () ... Resources.Push (resources) V (resources_sem)