Je pense que les deux font le même travail, comment décider lequel utiliser pour la synchronisation ?
Superbe explication... J'ai un doute concernant le spinlock, c'est-à-dire que je peux utiliser un spinlock dans un ISR ? si non, pourquoi pas ?
Je pense que les deux font le même travail, comment décider lequel utiliser pour la synchronisation ?
La théorie
En théorie, lorsqu'un thread essaie de verrouiller un mutex et qu'il n'y parvient pas, parce que le mutex est déjà verrouillé, il se met en sommeil, permettant immédiatement à un autre thread de s'exécuter. Il continuera à dormir jusqu'à ce qu'il soit réveillé, ce qui sera le cas lorsque le mutex sera déverrouillé par le thread qui détenait le verrou auparavant. Lorsqu'un thread essaie de verrouiller un spinlock et qu'il n'y parvient pas, il réessaie continuellement de le verrouiller, jusqu'à ce qu'il y parvienne finalement ; il ne permettra donc pas à un autre thread de prendre sa place (toutefois, le système d'exploitation passera de force à un autre thread, une fois que le quantum de temps d'exécution du CPU du thread actuel aura été dépassé, bien sûr).
Le problème
Le problème avec les mutex est que la mise en sommeil et le réveil des threads sont deux opérations plutôt coûteuses, qui nécessitent un grand nombre d'instructions CPU et prennent donc un certain temps. Si le mutex n'est verrouillé que pour un temps très court, le temps passé à mettre un thread en sommeil et à le réveiller pourrait dépasser de loin le temps pendant lequel le thread a effectivement dormi et pourrait même dépasser le temps que le thread aurait perdu en interrogeant constamment un spinlock. D'autre part, l'interrogation d'un spinlock gaspille constamment du temps CPU et si le verrou est maintenu pendant une période plus longue, cela gaspillera beaucoup plus de temps CPU et il aurait été bien mieux que le thread dorme à la place.
La solution
L'utilisation de spinlocks sur un système à un seul cœur/un seul processeur n'a généralement aucun sens, car tant que le polling du spinlock bloque le seul cœur de processeur disponible, aucun autre thread ne peut s'exécuter et comme aucun autre thread ne peut s'exécuter, le verrou ne sera pas déverrouillé non plus. Autrement dit, un spinlock ne fait que gaspiller du temps CPU sur ces systèmes sans aucun avantage réel. Si le thread avait été mis en veille, un autre thread aurait pu s'exécuter en même temps, débloquant éventuellement le verrou et permettant au premier thread de continuer le traitement, une fois qu'il se serait réveillé.
Sur un système multi-core/multi-CPU, avec de nombreux verrous qui ne sont maintenus que pour une très courte durée, le temps perdu à mettre constamment les threads en veille et à les réveiller peut diminuer sensiblement les performances d'exécution. En utilisant les spinlocks, les threads ont la possibilité de tirer parti de l'intégralité de leur quantum de temps d'exécution (ils ne se bloquent toujours que pendant une très courte période, puis poursuivent immédiatement leur travail), ce qui permet d'augmenter considérablement le débit de traitement.
La pratique
Étant donné que, très souvent, les programmeurs ne peuvent pas savoir à l'avance si les mutex ou les spinlocks seront préférables (par exemple, parce que le nombre de cœurs de l'architecture cible est inconnu), et que les systèmes d'exploitation ne peuvent pas non plus savoir si un certain morceau de code a été optimisé pour les environnements à un ou plusieurs cœurs, la plupart des systèmes ne font pas de distinction stricte entre les mutex et les spinlocks. En fait, la plupart des systèmes d'exploitation modernes ont des mutex hybrides et des spinlocks hybrides. Qu'est-ce que cela signifie concrètement ?
Un mutex hybride se comporte d'abord comme un spinlock sur un système multi-core. Si un thread ne peut pas verrouiller le mutex, il ne sera pas mis en sommeil immédiatement, puisque le mutex pourrait être déverrouillé assez rapidement, donc le mutex se comportera d'abord exactement comme un spinlock. Ce n'est que si le verrou n'a toujours pas été obtenu après un certain temps (ou de nouvelles tentatives ou tout autre facteur de mesure) que le thread est réellement mis en sommeil. Si le même code s'exécute sur un système ne comportant qu'un seul cœur, le mutex ne sera pas spinlocké, bien que, comme nous l'avons vu plus haut, cela ne serait pas avantageux.
Au début, un spinlock hybride se comporte comme un spinlock normal, mais pour éviter de gaspiller trop de temps CPU, il peut avoir une stratégie de retrait. En général, il ne met pas le thread en sommeil (puisque vous ne voulez pas que cela se produise lorsque vous utilisez un spinlock), mais il peut décider d'arrêter le thread (soit immédiatement, soit après un certain temps ; cela s'appelle "yielding") et permettre à un autre thread de s'exécuter, augmentant ainsi les chances que le spinlock soit débloqué (vous avez toujours les coûts d'un changement de thread mais pas les coûts de mise en sommeil et de réveil d'un thread).
Résumé
En cas de doute, utilisez les mutex, ils sont généralement le meilleur choix et la plupart des systèmes modernes permettent de les spinlocker pendant une très courte période, si cela semble bénéfique. L'utilisation de spinlocks peut parfois améliorer les performances, mais seulement dans certaines conditions et le fait que vous ayez des doutes m'indique plutôt que vous ne travaillez pas actuellement sur un projet où un spinlock pourrait être bénéfique. Vous pourriez envisager d'utiliser votre propre "objet verrou", qui peut soit utiliser un spinlock ou un mutex en interne (par exemple, ce comportement pourrait être configurable lors de la création d'un tel objet), utiliser initialement des mutex partout et si vous pensez que l'utilisation d'un spinlock quelque part pourrait vraiment aider, faites un essai et comparez les résultats (par exemple, en utilisant un profileur), mais assurez-vous de tester les deux cas, un système à un seul cœur et un système à plusieurs cœurs avant de sauter aux conclusions (et éventuellement différents systèmes d'exploitation, si votre code sera multiplateforme).
En fait, ce n'est pas spécifique à iOS, mais iOS est la plateforme sur laquelle la plupart des développeurs peuvent être confrontés à ce problème : si votre système dispose d'un planificateur de threads qui ne garantit pas que chaque thread, quelle que soit sa priorité, aura finalement une chance de s'exécuter, les spinlocks peuvent conduire à des blocages permanents. L'ordonnanceur iOS distingue différentes classes de threads et les threads d'une classe inférieure ne s'exécuteront que si aucun thread d'une classe supérieure ne veut s'exécuter également. Il n'y a pas de stratégie de retrait pour cela, donc si vous avez en permanence des threads de classe supérieure disponibles, les threads de classe inférieure n'obtiendront jamais de temps CPU et n'auront donc aucune chance d'effectuer un travail.
Le problème est le suivant : Votre code obtient un spinlock dans un thread de classe à faible priorité et pendant qu'il est au milieu de ce lock, le quantum de temps est dépassé et le thread s'arrête. La seule façon de libérer à nouveau ce spinlock est de permettre à ce thread de classe inférieure d'obtenir à nouveau du temps CPU, mais cela n'est pas garanti. Vous pouvez avoir quelques threads de classe élevée qui veulent constamment s'exécuter et le planificateur de tâches leur donnera toujours la priorité. L'un d'entre eux peut tomber sur le spinlock et essayer de l'obtenir, ce qui n'est bien sûr pas possible, et le système le fera céder. Le problème est le suivant : Un thread qui a cédé est immédiatement disponible pour fonctionner à nouveau ! Ayant un prio plus élevé que le thread détenant le verrou, le thread détenant le verrou n'a aucune chance d'obtenir du temps d'exécution CPU. Soit un autre thread obtiendra du temps d'exécution, soit le thread qui vient de céder.
Pourquoi ce problème ne se produit-il pas avec les mutex ? Lorsque le thread à haute priorité ne peut pas obtenir le mutex, il ne cède pas, il peut tourner un peu mais sera finalement mis en sommeil. Un thread en sommeil n'est pas disponible pour fonctionner jusqu'à ce qu'il soit réveillé par un événement, par exemple un événement comme le déblocage du mutex qu'il attendait. Apple est conscient de ce problème et a déprécié la fonction OSSpinLock
en conséquence. Le nouveau verrou est appelé os_unfair_lock
. Ce verrou évite la situation mentionnée ci-dessus car il est conscient des différentes classes de priorité des threads. Si vous êtes sûr que l'utilisation de spinlocks est une bonne idée dans votre projet iOS, utilisez celui-là. Restez à l'écart de OSSpinLock
! Et en aucun cas n'implémentez vos propres spinlocks dans iOS ! En cas de doute, utilisez un mutex. macOS n'est pas concerné par ce problème car il dispose d'un planificateur de threads différent qui ne permet à aucun thread (même les threads à faible priorité) d'être "à sec" sur le temps CPU, mais la même situation peut se produire et entraîner des performances très médiocres. OSSpinLock
est déprécié sur macOS également.
Superbe explication... J'ai un doute concernant le spinlock, c'est-à-dire que je peux utiliser un spinlock dans un ISR ? si non, pourquoi pas ?
@Mecki Si je ne me trompe pas, je crois que vous avez suggéré dans votre réponse que le découpage temporel ne se produit que sur les systèmes à processeur unique. Ce n'est pas le cas ! Vous pouvez utiliser un verrou tournant sur un système monoprocesseur et il tournera jusqu'à ce que son quantum de temps expire. Ensuite, un autre thread de même priorité peut prendre le relais (tout comme ce que vous avez décrit pour les systèmes multiprocesseurs).
@fumoboy007 "et il tournera jusqu'à ce que son quantum de temps expire" // Ce qui signifie que vous gaspillez du temps CPU/de l'énergie de la batterie pour absolument rien sans aucun bénéfice, ce qui est complètement débile. Et non, je n'ai dit nulle part que le time slicing ne se produit que sur les systèmes à un seul cœur, j'ai dit que sur les systèmes à un seul cœur, il y a UNIQUEMENT le découpage du temps, alors qu'il y a REAL le parallélisme sur les systèmes multicœurs (et aussi le time slicing, mais sans rapport avec ce que j'ai écrit dans ma réponse) ; de plus, vous n'avez absolument pas compris ce qu'est un spinlock hybride et pourquoi il fonctionne bien sur les systèmes mono- et multicœurs.
Dans la continuité de la suggestion de Mecki, cet article pthread mutex vs pthread spinlock sur le blog d'Alexander Sandler, Alex on Linux montre comment la spinlock
& mutexes
peut être implémenté pour tester le comportement en utilisant #ifdef.
Toutefois, assurez-vous de prendre la décision finale sur la base de votre observation, car l'exemple donné est un cas isolé, les exigences de votre projet et votre environnement peuvent être totalement différents.
La réponse de Mecki est tout à fait pertinente. Cependant, sur un processeur unique, l'utilisation d'un spinlock peut être utile lorsque la tâche attend que le verrou soit donné par une routine de service d'interruption. L'interruption transfère le contrôle à l'ISR, qui prépare la ressource pour que la tâche en attente puisse l'utiliser. Elle se termine en libérant le verrou avant de redonner le contrôle à la tâche interrompue. La tâche qui tourne trouve le spinlock disponible et continue.
Veuillez également noter que dans certains environnements et conditions (comme sous Windows avec un niveau de distribution >= DISPATCH LEVEL), vous ne pouvez pas utiliser les mutex mais plutôt les spinlock. Sur unix - même chose.
Voici une question équivalente sur le site concurrent stackexchange unix : https://unix.stackexchange.com/questions/5107/why-are-spin-locks-good-choices-in-linux-kernel-design-instead-of-something-more
Informations sur le dispatching sur les systèmes Windows : http://download.microsoft.com/download/e/b/a/eba1050f-a31d-436b-9281-92cdfeae4b45/IRQL_thread.doc
Les mécanismes de synchronisation Spinlock et Mutex sont aujourd'hui très courants pour être vus.
Pensons d'abord à Spinlock.
En fait, il s'agit d'une action d'attente d'occupation, ce qui signifie que nous devons attendre qu'un verrou spécifique soit libéré avant de pouvoir passer à l'action suivante. Conceptuellement très simple, alors que sa mise en œuvre n'est pas le cas. Par exemple : Si le verrou n'a pas été libéré, le thread a été échangé et s'est retrouvé en état de sommeil, que faire ? Comment gérer les verrous de synchronisation lorsque deux threads demandent simultanément l'accès ?
En général, l'idée la plus intuitive est de traiter la synchronisation via une variable pour protéger la section critique. Le concept de Mutex est similaire, mais ils restent différents. Concentrez-vous sur : L'utilisation du CPU. Spinlock consomme du temps CPU pour attendre de faire l'action, et donc, nous pouvons résumer la différence entre les deux :
Dans les environnements multicœurs homogènes, si le temps passé sur la section critique est faible, il faut utiliser Spinlock, car nous pouvons réduire le temps de changement de contexte. (La comparaison avec un seul cœur n'est pas importante, car certains systèmes mettent en œuvre Spinlock au milieu de la commutation).
Sous Windows, l'utilisation de Spinlock fera passer le thread à DISPATCH_LEVEL, ce qui dans certains cas n'est pas autorisé, donc cette fois nous avons dû utiliser un Mutex (APC_LEVEL).
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.
1 votes
Duplicata possible de Spinlock contre Sémaphore !
13 votes
Mutex et Semaphore ne sont pas la même chose, donc je ne pense pas que ce soit un doublon. La réponse de l'article référencé l'indique correctement. Pour plus de détails, voir barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore