Je pense que les deux font le même travail, comment décidez-vous lequel utiliser pour la synchronisation?
La théorie
En théorie, lorsqu'un thread tente de verrouiller un mutex sans succès, car il est déjà verrouillé, il se met en veille, permettant ainsi immédiatement à un autre thread de s'exécuter. Il continuera à dormir jusqu'à ce qu'il soit réveillé, ce qui sera le cas une fois que le mutex sera déverrouillé par le fil qui tenait le verrou auparavant. Lorsqu'un thread essaie de verrouiller un spinlock sans succès, il réessaie continuellement de le verrouiller jusqu'à ce qu'il réussisse. ainsi, il ne permettra pas à un autre thread de prendre sa place (toutefois, le système d'exploitation basculera de force sur un autre thread, une fois que le quantum d'exécution du processeur en cours aura été dépassé).
Le problème
Le problème avec les mutex est que mettre les threads en veille et les réveiller sont des opérations assez coûteuses, elles nécessiteront beaucoup d’instructions de la part du processeur et prendront donc un certain temps. Si à présent le mutex n'était verrouillé que pendant très peu de temps, le temps passé à endormir un fil et à le réveiller pourrait dépasser le temps pendant lequel le fil a réellement dormi et pourrait même dépasser le temps que le fil prendrait. ont gaspillé en scrutant constamment sur un spinlock. D'autre part, l'interrogation sur un spinlock fera constamment perdre du temps de calcul et si le verrouillage est maintenu plus longtemps, le temps de calcul sera beaucoup plus long et il aurait été beaucoup mieux si le thread dormait à la place.
La solution
L'utilisation de spinlocks sur un système mono-cœur/mono-processeur n'a généralement aucun sens. En effet, tant que l'interrogation du blocage des verrous bloque le seul cœur du processeur disponible, aucun autre thread ne peut s'exécuter et aucun autre ne peut s'exécuter, le verrou ne être débloqué non plus. IOW, un spinlock ne gaspille que du temps CPU sur ces systèmes sans aucun avantage réel. Si le thread avait été mis en veille à la place, un autre thread aurait pu être exécuté en même temps, éventuellement en déverrouillant le verrou et en permettant ensuite au premier thread de poursuivre le traitement, une fois qu'il s'est réveillé.
Sur des systèmes multicœurs/multi-processeurs, avec beaucoup de verrous maintenus pendant très peu de temps, le temps perdu à mettre constamment les threads en veille et à les réveiller peut réduire sensiblement les performances d'exécution. Lorsqu'ils utilisent plutôt les spinlocks, les threads ont la possibilité de tirer parti de leur quantum d'exécution complet (ne bloquant toujours que pendant une très courte période, puis poursuivant immédiatement leur travail), ce qui entraîne un débit de traitement beaucoup plus élevé.
La pratique
Comme les programmeurs ne peuvent souvent pas savoir à l’avance si les mutex ou les spinlocks seront meilleurs (par exemple, le nombre de cœurs de processeur de l’architecture cible n’est pas connu), ni les systèmes d’exploitation ne peuvent savoir si un code donné a été optimisé pour un processeur simple ou principal. Dans les environnements multicœurs, la plupart des systèmes ne font pas la distinction entre mutex et 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 réellement?
Un mutex hybride se comporte d'abord comme un spinlock sur un système multicœur. Si un thread ne peut pas verrouiller le mutex, il ne sera pas immédiatement mis en veille, car le mutex sera bientôt déverrouillé. Par conséquent, 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 plusieurs tentatives ou tout autre facteur de mesure) que le fil est réellement mis en veille. Si le même code est exécuté sur un système ne comportant qu'un seul noyau, le mutex ne sera pas verrouillable, mais, comme indiqué ci-dessus, cela ne serait pas avantageux.
Un spinlock hybride se comporte au départ comme un spinlock normal, mais pour éviter de gaspiller trop de temps de calcul, il peut adopter une stratégie de secours. Cela ne mettra généralement pas le fil en veille (car vous ne voulez pas que cela se produise avec un verrou tournant), mais il peut décider d'arrêter le fil (immédiatement ou après un certain temps) et permettre à un autre fil de s'exécuter. , augmentant ainsi les chances que le spinlock soit déverrouillé (un commutateur de fil pur est généralement moins coûteux que celui qui implique de mettre un fil en veille et de le réveiller plus tard, mais pas de loin).
Résumé
En cas de doute, utilisez des mutex, ils sont généralement le meilleur choix et la plupart des systèmes modernes leur permettront de se verrouiller pendant une très courte période, si cela semble bénéfique. L'utilisation des spinlocks peut parfois améliorer les performances, mais seulement dans certaines conditions et le fait que vous doutiez me dit plutôt que vous ne travaillez sur aucun projet où un spinlock pourrait être bénéfique. Vous pouvez envisager d'utiliser votre propre "objet de verrouillage", qui peut utiliser un spinlock ou un mutex en interne (par exemple, ce comportement peut être configurable lors de la création d'un tel objet), utilisez d'abord des mutex partout et si vous pensez que l'utilisation d'un spinlock quelque part peut réellement essayez, comparez les résultats (par exemple, avec un profileur), mais veillez à tester les deux cas, un système monocœur et un système multicœur avant de sauter rapidement aux conclusions (et éventuellement à des systèmes d’exploitation différents, le cas échéant). sera multi-plateforme).
En fait, ce n’est pas spécifique à iOS, mais iOS est la plate-forme sur laquelle la plupart des développeurs peuvent faire face à ce problème: Si votre système dispose d’un planificateur de threads, cela ne garantit pas que tout thread, quelle que soit sa priorité, aura éventuellement la possibilité de s’exécuter, alors les spinlocks peuvent mener à des blocages permanents. Le planificateur iOS distingue différentes classes de threads et les threads d'une classe inférieure ne s'exécutent que si aucun thread d'une classe supérieure ne souhaite également s'exécuter. Il n'y a pas de stratégie de secours pour cela, donc si vous avez en permanence des threads de grande classe disponibles, les threads de basse classe n'obtiendront jamais de temps processeur et donc aucune chance de travailler.
Le problème se présente comme suit: Votre code obtient un spinlock dans un thread de classe prio faible. Lorsqu'il est au milieu de ce verrou, le temps écoulé est dépassé et le thread cesse de fonctionner. La seule façon de relâcher ce verrou verrouillable consiste à obtenir de nouveau le temps processeur pour ce thread de classe prio faible, mais cela n’est pas garanti. Vous pouvez avoir plusieurs threads de classe prio élevés que vous souhaitez exécuter en permanence et le planificateur de tâches les hiérarchisera toujours par ordre de priorité. L'un d'entre eux risque de se heurter au spinlock et d'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 être exécuté à nouveau! Ayant un prio plus élevé que le thread qui tient le verrou, le thread qui tient le verrou n'a aucune chance d'obtenir le temps d'exécution du processeur. Soit un autre thread obtiendra le 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 fil à haute valeur ajoutée ne peut pas obtenir le mutex, il ne cédera pas, il risque de tourner un peu mais finira par s'endormir. Un thread en veille n'est pas disponible pour être exécuté tant qu'il n'a pas été réveillé par un événement, par exemple. un événement comme le mutex en cours de déverrouillage qu’il attendait. Apple est conscient de ce problème et a donc pour conséquence obsolète OSSpinLock
. Le nouveau verrou s'appelle os_unfair_lock
. Ce verrou évite la situation mentionnée ci-dessus car il est conscient des différentes classes de priorité de thread. Si vous êtes certain que l'utilisation de spinlocks est une bonne idée dans votre projet iOS, utilisez-le. Restez à l'écart de OSSpinLock
! Et n'implémentez jamais vos propres verrous de verrouillage dans iOS! En cas de doute, utilisez un mutex! macOS n'est pas concerné par ce problème car il a un programmateur de thread différent qui ne permet à aucun thread (même les prio faibles) de "fonctionner à sec" lors du temps processeur, mais la même situation peut se produire là-bas et conduire à de très mauvaises performances. ainsi, OSSpinLock
est également déconseillé sur macOS.
Poursuivant la suggestion de Mecki, cet article pthread mutex vs pthread spinlock sur le blog de Alexander Sandler, Alex sur Linux montre comment les spinlock
& mutexes
peuvent être implémentés pour tester le comportement à l'aide de # ifdef.
Cependant, assurez-vous de prendre l'appel final en fonction de votre observation, sachant que l'exemple donné est un cas isolé, les exigences de votre projet et l'environnement peuvent être totalement différents.
La réponse de Mecki la décrit assez bien. Cependant, sur un seul processeur, l'utilisation d'un verrou tournant peut être utile lorsque la tâche attend que le verrou soit attribué par une routine de service d'interruption. L'interruption transférerait le contrôle à l'ISR, ce qui préparerait la ressource à être utilisée par la tâche en attente. Cela finirait par relâcher le verrou avant de rendre le contrôle à la tâche interrompue. La tâche en rotation trouve le spinlock disponible et continue.
Veuillez également noter que, dans certains environnements et conditions (tels que l'exécution sur Windows au niveau de la répartition> = NIVEAU DE RÉPARTITION), vous ne pouvez pas utiliser un mutex, mais plutôt un spinlock. Sur unix - même chose.
Voici une question équivalente sur le site concurrent concurrent stackexchange unix: https://unix.stackexchange.com/questions/5107/why-are-spin-locks-good-choices-in-linux-kernel -design-place-de-quelque chose-plus
Informations sur la distribution 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 très courants aujourd'hui.
Pensons d'abord à Spinlock.
Fondamentalement, il s’agit d’une action d’attente chargée, ce qui signifie que nous devons attendre qu’un verrou spécifié soit libéré avant de pouvoir passer à l’action suivante. Conceptuellement très simple, lors de la mise en œuvre ce n'est pas sur le cas. Par exemple: Si le verrou n'a pas été libéré, le thread a été échangé et est passé en mode veille, devrions-nous nous en occuper? Comment gérer les verrous de synchronisation lorsque deux threads demandent simultanément l'accès?
Généralement, l'idée la plus intuitive est de gérer la synchronisation via une variable pour protéger la section critique. Le concept de Mutex est similaire, mais ils sont toujours différents. Concentrez-vous sur: l'utilisation du processeur. Spinlock consomme du temps CPU pour attendre l’action, et nous pouvons donc résumer la différence entre les deux:
Dans les environnements multicœurs homogènes, si le temps consacré aux sections critiques est plus petit que l’utilisation de Spinlock, vous pouvez réduire le temps de changement de contexte. (La comparaison monocœur n'est pas importante, car certains systèmes implémentent Spinlock au milieu du commutateur.)
Dans Windows, l’utilisation de Spinlock mettra à niveau le fil de discussion vers DISPATCH_LEVEL, ce qui, dans certains cas, peut ne pas être autorisé. Cette fois, nous avons donc dû utiliser un mutex (APC_LEVEL).