Serrure, mutex, sémaphore ... quelle différence?
j'ai entendu ces mots liés à la programmation simultanée, mais quelle est la différence entre eux?
8 réponses
une serrure permet à un seul thread d'entrer dans la pièce qui est verrouillée et la serrure n'est pas partagée avec d'autres processus.
Un mutex est la même qu'une serrure, mais il peut être à l'échelle du système (partagé par plusieurs processus).
A sémaphore fait la même chose qu'un mutex mais permet à x nombre de threads d'entrer, cela peut être utilisé par exemple pour limiter le nombre de cpu, io ou des tâches intensives en ram courant en même temps.
vous avez également lire/écrire serrures qui permet soit le nombre illimité de lecteurs ou un écrivain à un moment donné.
Il y a beaucoup d'idées fausses au sujet de ces mots.
C'est à partir d'un précédent billet ( https://stackoverflow.com/a/24582076/3163691 ) qui s'adapte superbe ici:
1) section critique = objet utilisateur utilisé pour permettre l'exécution juste un fil actif de beaucoup d'autres dans un processus . Les autres fils non sélectionnés sont portés à sleep .
[Sans interprocessus capacité, très primitive de l'objet].
2) Mutex sémaphore (alias Mutex) = objet du noyau utilisé pour permettre l'exécution de juste un fil actif de beaucoup d'autres, parmi les différents processus . Les autres fils non sélectionnés sont portés à sleep . Cet objet prend en charge la propriété de thread, la notification de terminaison de thread, la récursion (plusieurs appels 'acquire' à partir du même thread) et l '"évitement d'inversion de priorité".
[Interprocessus capacité, très sûr à utiliser, une sorte de "haut niveau" de la synchronisation de l'objet].
3) sémaphore à compter (Alias sémaphore) = amande objet utilisé pour permettre l'exécution de un groupe de threads actifs de beaucoup d'autres. Les autres fils non sélectionnés sont portés à sleep .
[Interprocessus capacité cependant pas très sûr à utiliser car il manque suivantes mutex attributs: fil notification de résiliation, la récursivité?, 'inversion de priorité évitement"?, etc.]
4) Et maintenant, en parlant de 'spinlocks', d'abord quelques définitions:
région critique= région de mémoire partagée par 2 processus ou plus.
Lock= Une variable dont la valeur permet ou interdit l'entrée à une "critique de la région". (Il pourrait être mis en œuvre comme un simple indicateur booléen').
Occupé attente= Continuellement test d'une variable jusqu'à une certaine valeur s'affiche.
enfin:
Spin-lock (alias Spinlock) = A lock qui utilise busy waiting . (L'acquisition de la serrure est faite par xchg ou similaire opérations atomiques ).
[Sans fil à dormir, surtout utilisé au noyau niveau seulement. Inefficace pour le code au niveau de L'utilisateur].
comme dernier commentaire, Je ne suis pas sûr mais je peux parier que les 3 premiers objets de synchronisation (#1, #2 et #3) utilisent cette simple bête (#4) dans le cadre de leur implémentation.
bonne journée!.
, les Références:
- Concepts en temps réel pour les systèmes embarqués par Qing Li avec Caroline Yao (CMP Books).
-Systèmes d'Exploitation Modernes (3e) par Andrew Tanenbaum (Pearson Internationale de l'Éducation).
-Programmation d'Applications pour Microsoft Windows (4e) par Jeffrey Richter (Microsoft Programmation de la Série).
aussi, vous pouvez jeter un oeil à regarder: https://stackoverflow.com/a/24586803/3163691
regardez Multithreading Tutoriel par Jean-Kopplin.
dans la section synchronisation entre les fils , il explique les différences entre événement, serrure, mutex, sémaphore ,minuteur de waitable
A mutex peut être détenu par un seul fil à la fois, permettant fils à coordonner l'accès mutuellement exclusif à une ressource partagée
objets section Critique fournir la synchronisation similaire à celle fourni par les objets mutex, sauf que les objets de la section critique peuvent être utilisé uniquement par les fils d'un seul processus
une autre différence entre un mutex et un section critique est que si l'objet de la section critique appartient actuellement à un autre thread,
EnterCriticalSection()
attend indéfiniment pour la propriété alorsWaitForSingleObject()
, qui est utilisé avec un mutex, vous permet de spécifiez un délaiA sémaphore maintient un compte entre zéro et une certaine valeur maximale, limiter le nombre de threads qui accèdent simultanément à un ressources partagées.
je vais essayer de le couvrir avec des exemples:
Lock: un exemple où vous utiliseriez lock
serait un dictionnaire partagé dans lequel des éléments (qui doivent avoir des Clés uniques) sont ajoutés.
La serrure permettrait de s'assurer qu'un thread n'entre pas dans le mécanisme de code qui vérifie que l'élément est dans le dictionnaire tandis qu'un autre thread (qui est dans la section critique) a déjà passé cette vérification et ajoute article. Si un autre thread essaie d'entrer un code verrouillé, il attendra (sera bloqué) jusqu'à ce que l'objet soit libéré.
private static readonly Object obj = new Object();
lock (obj) //after object is locked no thread can come in and insert item into dictionary on a different thread right before other thread passed the check...
{
if (!sharedDict.ContainsKey(key))
{
sharedDict.Add(item);
}
}
sémaphore: Supposons que vous ayez un pool de connexions, alors un seul thread pourrait réserver un élément dans le pool en attendant que le sémaphore obtienne une connexion. Il utilise alors la connexion et quand le travail est fait libère la connexion en libérant le sémaphore.
Code exemple que j'aime est celui de videur donné par @Patric - voici:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TheNightclub
{
public class Program
{
public static Semaphore Bouncer { get; set; }
public static void Main(string[] args)
{
// Create the semaphore with 3 slots, where 3 are available.
Bouncer = new Semaphore(3, 3);
// Open the nightclub.
OpenNightclub();
}
public static void OpenNightclub()
{
for (int i = 1; i <= 50; i++)
{
// Let each guest enter on an own thread.
Thread thread = new Thread(new ParameterizedThreadStart(Guest));
thread.Start(i);
}
}
public static void Guest(object args)
{
// Wait to enter the nightclub (a semaphore to be released).
Console.WriteLine("Guest {0} is waiting to entering nightclub.", args);
Bouncer.WaitOne();
// Do some dancing.
Console.WriteLine("Guest {0} is doing some dancing.", args);
Thread.Sleep(500);
// Let one guest out (release one semaphore).
Console.WriteLine("Guest {0} is leaving the nightclub.", args);
Bouncer.Release(1);
}
}
}
Mutex C'est assez Semaphore(1,1)
et souvent utilisé à l'échelle mondiale (application large sinon sans doute lock
est plus approprié). L'une utiliserait global Mutex
lors de la suppression de noeud d'une liste globalement accessible (dernière chose que vous voulez un autre thread pour faire quelque chose pendant que vous supprimez le noeud). Lorsque vous acquérez Mutex
si un fil différent essaie d'acquérir le même Mutex
il sera endormi jusqu'à ce que le même fil qui a acquis le Mutex
le libère.
Bon exemple sur la création mondiale de mutex est par @deepee
class SingleGlobalInstance : IDisposable
{
public bool hasHandle = false;
Mutex mutex;
private void InitMutex()
{
string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
string mutexId = string.Format("Global\{{{0}}}", appGuid);
mutex = new Mutex(false, mutexId);
var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
var securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
mutex.SetAccessControl(securitySettings);
}
public SingleGlobalInstance(int timeOut)
{
InitMutex();
try
{
if(timeOut < 0)
hasHandle = mutex.WaitOne(Timeout.Infinite, false);
else
hasHandle = mutex.WaitOne(timeOut, false);
if (hasHandle == false)
throw new TimeoutException("Timeout waiting for exclusive access on SingleInstance");
}
catch (AbandonedMutexException)
{
hasHandle = true;
}
}
public void Dispose()
{
if (mutex != null)
{
if (hasHandle)
mutex.ReleaseMutex();
mutex.Dispose();
}
}
}
puis utiliser comme:
using (new SingleGlobalInstance(1000)) //1000ms timeout on global lock
{
//Only 1 of these runs at a time
GlobalNodeList.Remove(node)
}
J'espère que cela vous fera gagner du temps.
la plupart des problèmes peuvent être résolus en utilisant (i) seulement des serrures, (ii) juste des sémaphores, ... ou (iii) une combinaison des deux! Comme vous avez pu le découvrir, ils sont très similaires: tous les deux empêchent conditions de course , les deux ont des opérations acquire()
/ release()
, les deux provoquent zéro ou plus de threads à devenir bloqué/suspecté...
En réalité, la différence cruciale réside uniquement sur comment ils verrouillent et déverrouillent .
- Un lock (ou mutex ) a deux états (0 ou 1). Elle peut être déverrouillée ou verrouillée . Ils sont souvent utilisés pour s'assurer qu'un seul thread entre dans une section critique à la fois.
- Un sémaphore possède de nombreux états (0, 1, 2, ...). Il peut être verrouillé (état 0), ou déverrouillé (états 1, 2, 3,...). Un ou plusieurs sémaphores sont souvent utilisés ensemble pour s'assurer qu'un seul thread entre dans une section critique précisément lorsque le nombre d'unités d'une ressource n'a pas atteint une valeur particulière (soit en comptant vers le bas jusqu'à cette valeur, soit en comptant jusqu'à cette valeur).
pour les deux serrures/sémaphores, en essayant d'appeler acquire()
pendant que le primitif est dans l'état 0 provoque la suspension du fil d'invocation. Pour les serrures-tentatives d'acquisition la serrure est dans l'état 1 sont réussis. Pour les sémaphores-tentatives d'acquisition de l'écluse dans les états {1, 2, 3,...} sont couronnées de succès.
Pour les écluses de l'état l'état 0, si même thread qui avait auparavant appelé acquire()
, demande maintenant à la libération, puis la sortie est réussi. Si un thread différent a essayé cela -- cela dépend de l'implémentation / bibliothèque quant à ce qui se passe (habituellement la tentative ignorée ou une erreur est lancée). Pour les sémaphores dans l'état 0, n'importe quel thread peut appeler release et il sera réussi (quel que soit le thread utilisé précédemment acquérir pour mettre le sémaphore dans l'état 0).
de la discussion précédente, nous pouvons voir que les serrures ont une notion de propriétaire (le seul fil qui peut appeler la libération est le propriétaire), alors que les sémaphores n'ont pas de propriétaire (n'importe quel fil peut appeler la libération sur un sémaphore).
ce qui cause beaucoup de confusion est que, dans la pratique, ils sont nombreuses variations de cette définition de haut niveau.
des variations Importantes à prendre en considération :
- Comment doit-on appeler
acquire()
/release()
? -- [varie massivement ] - lock / sémaphore utilisez une "queue" ou un " jeu " pour se souvenir des fils qui attendent?
- votre serrure/sémaphore peut-il être partagé avec des fils d'autres processus?
- votre serrure est-elle"rentrante"? -- [normalement oui].
- votre serrure est-elle"bloquante/non-bloquante"? -- [Normalement non-blocage sont utilisés comme Serrures de blocage (aka spin-locks) cause busy waiting].
- comment vous assurez-vous que les opérations sont "atomiques"?
cela dépend de votre livre / conférencier / langue / bibliothèque / environnement.
Voici un tour rapide notant comment certaines langues répondent à ces détails.
C, C++ ( pthreads )
- Un mutex est implémenté via
pthread_mutex_t
. Par défaut, ils ne peuvent être partagés avec aucun autre processus (PTHREAD_PROCESS_PRIVATE
), mais les mutex ont un attribut appelé pshared . Lorsqu'il est défini, ainsi le mutex est partagé entre les processus (PTHREAD_PROCESS_SHARED
). - Un lock est la même chose qu'un mutex.
- Un sémaphore est mis en œuvre par l'intermédiaire de
sem_t
. Semblable à mutex, sémaphores peut être partagé entre trois couches de plusieurs processus ou gardé privé pour les fils d'un seul processus. Cela dépend de l'argument pshared fourni àsem_init
.
python ( threading.py )
- a lock (
threading.RLock
) est la plupart du temps la même que C/C++pthread_mutex_t
s. Tous les deux sont rentrant . Cela signifie ils ne peuvent être déverrouillés que par le même fil qui l'a verrouillé. Il est le cas quesem_t
sémaphores,threading.Semaphore
sémaphores ettheading.Lock
serrures sont Non rentrant -- car il est le cas n'importe quel fil peut effectuer déverrouiller le verrou / vers le bas du Sémaphore. - Un mutex est le même qu'un verrou (le terme n'est pas utilisé souvent en python).
- Un sémaphore (
threading.Semaphore
) est essentiellement le même quesem_t
. Bien qu'avecsem_t
, une queue de thread ids est utilisée pour se souvenir de l'ordre dans lequel les threads sont devenus bloqués en essayant de le verrouiller alors qu'il est verrouillé. Quand un fil déverrouille un sémaphore, le premier fil dans la file (s'il y en a un) est choisi pour être le nouveau propriétaire. L'identifiant du thread est retiré de la file d'attente et le sémaphore est à nouveau verrouillé. Cependant, avecthreading.Semaphore
, un jeu est utilisé au lieu d'une file d'attente, ainsi l'ordre dans lequel les threads sont devenus bloqués n'est pas stocké -- n'importe quel thread dans l'ensemble peut être choisi pour être le propriétaire suivant.
Java ( java.util.concurrent )
- a lock (
java.util.concurrent.ReentrantLock
) est la plupart du temps la même que C/C++pthread_mutex_t
's, et Pythonthreading.RLock
en ce qu'il implémente également un lock de rentrée. Le partage de serrures entre les processus est plus difficile en Java en raison de la JVM agissant comme intermédiaire. Si un thread essaie de déverrouiller une serrure qu'il ne possède pas, unIllegalMonitorStateException
est lancé. - A mutex est la même chose qu'une serrure (le terme n'est pas utilisé souvent en Java).
- Un sémaphore (
java.util.concurrent.Semaphore
) est essentiellement le même quesem_t
etthreading.Semaphore
. Le constructeur pour les sémaphores Java accepte une équité paramètre booléen qui contrôle si utiliser un ensemble (false) ou une file (true) pour stocker les threads d'attente.
en théorie, les sémaphores sont souvent discutés, mais dans la pratique, les sémaphores ne sont pas tellement utilisés. Un sémaphore ne retient que l'état de un entier, donc souvent il est plutôt inflexible et beaucoup sont nécessaires à la fois -- causant des difficultés dans la compréhension du code. Aussi, le fait que tout" fil 1519320920 " peut libérer un sémaphore est parfois indésirable. À la place, on utilise des primitives / abstractions de synchronisation plus orientées objet / de niveau supérieur telles que les "variables de condition" et les "moniteurs".
Wikipedia a une grande section sur les différences entre les sémaphores et les Mutex :
Un mutex est essentiellement la même chose qu'un sémaphore binaire et utilise parfois la même implémentation de base. Les différences entre les ce sont:
Mutex ont un concept d'un propriétaire, qui est le processus qui a verrouillé le mutex. Seul le processus qui a verrouillé le mutex peut le déverrouiller. En revanche, un sémaphore n'a pas de notion de propriétaire. Tout le processus peut déverrouiller un sémaphore.
Contrairement aux sémaphores, les priorité sécurité de l'inversion. Depuis le mutex connaît son propriétaire actuel, il est possible de promouvoir la priorité du propriétaire toutes les fois qu'un la tâche prioritaire commence à attendre le mutex.
Les Mutexfournissent également sécurité d'effacement, lorsque le procédé de maintien du mutex ne peut pas être accidentellement supprimé. Les sémaphores ne fournissent pas ce.
je crois comprendre qu'un mutex ne peut être utilisé que dans un seul processus, mais à travers ses nombreux fils, alors qu'un sémaphore peut être utilisé à travers plusieurs processus, et à travers leurs ensembles correspondants de fils.
en outre, un mutex est binaire (il est soit verrouillé ou déverrouillé), alors qu'un sémaphore a une notion de comptage, ou une queue de plus d'un verrou et de demandes de déverrouillage.
quelqu'un Pourrait-il vérifier mon explication? Je parle dans le contexte de Linux, en particulier Red Hat Enterprise Linux (RHEL) version 6, qui utilise le noyau 2.6.32.
utilisant la programmation C sur une variante de Linux comme cas de base pour des exemples.
serrure:
• en général, un très simple de construire binaire dans l'opération de verrouillage ou de déverrouillage
* pas de concept de propriété du fil, de priorité, de séquence, etc.
* habituellement une serrure de spin où le fil vérifie en continu la disponibilité des serrures.
* habituellement s'appuie sur des opérations atomiques par exemple, Test-and-set, compare-and-swap, extraire et ajouter de etc.
• en général, nécessite un support matériel pour opération atomique.
Serrures De Fichiers:
* habituellement utilisé pour coordonner l'accès à un fichier via plusieurs processus.
• Plusieurs processus peuvent tenir le verrou en lecture cependant lorsqu'un processus détient le verrou d'écriture aucun autre processus n'est autorisé à acquisition d'une serrure en lecture ou en écriture.
"151900920 • * exemple: flock, fcntl etc..Mutex:
• les appels de fonction Mutex fonctionnent habituellement dans l'espace du noyau et donnent lieu à des appels système.
• Il utilise la notion de propriété. Seul le thread qui contient actuellement le mutex peut le déverrouiller.
• Mutex n'est pas récursif (Exception: PTHREAD_MUTEX_RECURSIVE).
"151900920 • * habituellement utilisé en Association avec des Variables de Condition et transmis comme arguments à par ex. pthread_cond_signal, pthread_cond_wait etc.* certains systèmes UNIX permettent à mutex d'être utilisé par plusieurs processus, bien que cela ne soit pas obligatoire sur tous les systèmes.
sémaphore:
• il s'agit d'un nombre entier maintenu par le noyau dont les valeurs ne peuvent pas tomber en dessous de zéro.
• Il peut être utilisé pour synchroniser les processus.
• La valeur du sémaphore peut être réglé à une valeur supérieure à 1, auquel cas la valeur indique le nombre de ressources disponibles.
• Un sémaphore dont la valeur est limitée à 1 et 0 est appelé un sémaphore binaire.