J'ai entendu ces mots liés à la programmation simultanée, mais quelle est la différence entre eux?
J'ai entendu ces mots liés à la programmation simultanée, mais quelle est la différence entre eux?
Réponses:
Un verrou permet à un seul thread d'entrer dans la partie verrouillée et le verrou n'est partagé avec aucun autre processus.
Un mutex est identique à un verrou mais il peut être à l'échelle du système (partagé par plusieurs processus).
Un sémaphore fait la même chose qu'un mutex mais permet à x nombre de threads d'entrer, ceci peut être utilisé par exemple pour limiter le nombre de tâches gourmandes en CPU, en io ou en RAM s'exécutant en même temps.
Pour un article plus détaillé sur les différences entre mutex et sémaphore, lisez ici .
Vous avez également des verrous en lecture / écriture qui permettent à tout moment un nombre illimité de lecteurs ou 1 écrivain.
Il y a beaucoup d'idées fausses concernant ces mots.
Ceci provient d'un article précédent ( https://stackoverflow.com/a/24582076/3163691 ) qui convient parfaitement ici:
1) Section critique = objet utilisateur utilisé pour permettre l'exécution d'un seul thread actif à partir de nombreux autres dans un même processus . Les autres threads non sélectionnés (@ acquisition de cet objet) sont mis en veille .
[Pas de capacité interprocessus, objet très primitif].
2) Mutex Semaphore (aka Mutex) = Objet noyau utilisé pour permettre l'exécution d'un seul thread actif parmi de nombreux autres, parmi différents processus . Les autres threads non sélectionnés (@ acquisition de cet objet) sont mis en veille . Cet objet prend en charge la propriété des threads, la notification de terminaison des threads, la récursivité (plusieurs appels «d'acquisition» à partir du même thread) et «l'évitement d'inversion de priorité».
[Capacité interprocessus, très sûre à utiliser, une sorte d'objet de synchronisation «de haut niveau»].
3) Counting Semaphore (aka Semaphore) = Objet noyau utilisé pour permettre l'exécution d' un groupe de threads actifs à partir de nombreux autres. Les autres threads non sélectionnés (@ acquisition de cet objet) sont mis en veille .
[La capacité interprocessus n'est cependant pas très sûre à utiliser car elle n'a pas les attributs «mutex» suivants: notification de terminaison de thread, récursivité?, «Évitement d'inversion de priorité»?, 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 autorise ou refuse l'entrée d'une «région critique». (Il pourrait être implémenté comme un simple «drapeau booléen»).
Attente occupée = Test continu d'une variable jusqu'à ce qu'une certaine valeur apparaisse.
Finalement:
Spin-lock (aka Spinlock) = Un verrou qui utilise l' attente occupée . (L'acquisition du verrou se fait par xchg ou des opérations atomiques similaires ).
[Aucun thread en veille, principalement utilisé au niveau du noyau uniquement. Ineffcient pour le code de niveau utilisateur].
En dernier commentaire, je ne suis pas sûr mais je peux vous parier quelques gros dollars que les 3 premiers objets de synchronisation ci-dessus (# 1, # 2 et # 3) utilisent cette bête simple (# 4) dans le cadre de leur mise en œuvre.
Bonne journée!.
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 Education International).
-Programmation d'applications pour Microsoft Windows (4e) par Jeffrey Richter (Microsoft Programming Series).
En outre, vous pouvez jeter un œil à l' adresse suivante : https://stackoverflow.com/a/24586803/3163691
La plupart des problèmes peuvent être résolus en utilisant (i) uniquement des verrous, (ii) uniquement des sémaphores, ..., ou (iii) une combinaison des deux! Comme vous l'avez peut-être découvert, ils sont très similaires: les deux empêchent les conditions de concurrence , les deux ont acquire()
/ release()
opérations, les deux provoquent le blocage / soupçon de zéro ou plusieurs threads ... Vraiment, la différence cruciale réside uniquement dans la façon dont ils se verrouillent et se déverrouillent .
Pour les deux verrous / sémaphores, essayer d'appeler acquire()
alors que la primitive est à l'état 0 entraîne la suspension du thread appelant . Pour les verrous - les tentatives pour acquérir le verrou dans l'état 1 ont réussi. Pour les sémaphores - les tentatives d'acquisition du verrou dans les états {1, 2, 3, ...} ont réussi.
Pour les verrous à l'état 0, si le même thread qui avait précédemment appelé acquire()
appelle maintenant la libération, la libération réussit. Si un thread différent a essayé cela - c'est à l'implémentation / bibliothèque de savoir ce qui se passe (généralement la tentative est ignorée ou une erreur est levée). Pour les sémaphores à l'état 0, n'importe quel thread peut appeler release et cela réussira (quel que soit le thread utilisé précédemment pour acquérir le sémaphore à l'état 0).
De la discussion précédente, nous pouvons voir que les verrous ont une notion de propriétaire (le seul thread qui peut appeler release est le propriétaire), tandis que les sémaphores n'ont pas de propriétaire (n'importe quel thread peut appeler release sur un sémaphore).
Ce qui cause beaucoup de confusion, c'est que, dans la pratique, il existe de nombreuses variantes de cette définition de haut niveau.
Variations importantes à considérer :
acquire()
/ release()
être appelé? - [Varie massivement ]Cela dépend de votre livre / professeur / langue / bibliothèque / environnement.
Voici un rapide tour d'horizon notant comment certaines langues répondent à ces détails.
pthread_mutex_t
. Par défaut, ils ne peuvent pas être partagés avec d'autres processus ( PTHREAD_PROCESS_PRIVATE
), cependant les mutex ont un attribut appelé pshared . Lorsqu'il est défini, le mutex est partagé entre les processus ( PTHREAD_PROCESS_SHARED
).sem_t
. Semblable aux mutex, les sémaphores peuvent être partagés entre des menaces de nombreux processus ou gardés privés pour les threads d'un seul processus. Cela dépend de l' argument pshared fourni à sem_init
.threading.RLock
) est essentiellement le même que C / C ++ pthread_mutex_t
. Les deux sont tous deux réentrants . Cela signifie qu'ils ne peuvent être déverrouillés que par le même fil qui l'a verrouillé. Il est vrai que les sem_t
sémaphores, les threading.Semaphore
sémaphores et les theading.Lock
verrous ne sont pas réentrants - car c'est le cas où n'importe quel thread peut effectuer un déverrouillage du verrouillage / descente du sémaphore.threading.Semaphore
) est essentiellement le même que sem_t
. Bien qu'avec sem_t
, une file d'attente d'ID de threads est utilisée pour se souvenir de l'ordre dans lequel les threads ont été bloqués lors de la tentative de verrouillage pendant qu'il est verrouillé. Lorsqu'un thread déverrouille un sémaphore, le premier thread de la file d'attente (s'il y en a un) est choisi pour être le nouveau propriétaire. L'identificateur de thread est retiré de la file d'attente et le sémaphore est à nouveau verrouillé. Cependant, avec threading.Semaphore
, un ensemble est utilisé à la place d'une file d'attente, donc l'ordre dans lequel les threads ont été bloqués n'est pas stocké - n'importe quel thread de l'ensemble peut être choisi pour être le prochain propriétaire.java.util.concurrent.ReentrantLock
) est essentiellement le même que celui de C / C ++ pthread_mutex_t
et de Python threading.RLock
en ce qu'il implémente également un verrou réentrant. Le partage de verrous entre les processus est plus difficile en Java car la JVM agit comme un intermédiaire. Si un thread essaie de déverrouiller un verrou qu'il ne possède pas, un IllegalMonitorStateException
est levé.java.util.concurrent.Semaphore
) est essentiellement le même que sem_t
et threading.Semaphore
. Le constructeur des sémaphores Java accepte un paramètre booléen d' équité qui contrôle s'il faut utiliser un ensemble (faux) ou une file d'attente (vrai) pour stocker les threads en attente. En théorie, les sémaphores sont souvent discutés, mais en pratique, les sémaphores ne sont pas tellement utilisés. Un sémaphore ne contient que l'état d' un entier, si souvent il est plutôt rigide et plusieurs sont nécessaires à la fois, ce qui rend difficile la compréhension du code. De plus, le fait que n'importe quel thread puisse libérer un sémaphore est parfois indésirable. Des primitives / abstractions de synchronisation plus orientées objet / de niveau supérieur telles que des "variables de condition" et des "moniteurs" sont utilisées à la place.
Jetez un oeil à Multithreading Tutorial par John Kopplin.
Dans la section Synchronisation entre les threads , il explique les différences entre les événements, les verrous, les mutex, les sémaphores et les temporisations.
Un mutex ne peut appartenir qu'à un seul thread à la fois, ce qui permet aux threads de coordonner l'accès mutuellement exclusif à une ressource partagée
Les objets de section critiques fournissent une synchronisation similaire à celle fournie par les objets mutex, sauf que les objets de section critiques ne peuvent être utilisés que par les threads d'un seul processus
Une autre différence entre un mutex et une section critique est que si l'objet de section critique appartient actuellement à un autre thread,
EnterCriticalSection()
attend indéfiniment la propriété tandis queWaitForSingleObject()
, qui est utilisé avec un mutex, vous permet de spécifier un délai d'expirationUn sémaphore conserve un nombre compris entre zéro et une valeur maximale, limitant le nombre de threads qui accèdent simultanément à une ressource partagée.
Je vais essayer de le couvrir d'exemples:
Verrouiller: 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.
Le verrou garantirait 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 se trouve dans la section critique) a déjà passé cette vérification et ajoute l'élément. 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: Disons que vous avez un pool de connexions, alors un seul thread peut réserver un élément dans le pool en attendant que le sémaphore obtienne une connexion. Il utilise ensuite la connexion et lorsque le travail est terminé, libère la connexion en libérant le sémaphore.
L'exemple de code que j'aime est celui d'un videur donné par @Patric - le 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 Il est à peu près Semaphore(1,1)
et souvent utilisé à l'échelle mondiale (à l'échelle de l'application sinon, il lock
est sans doute plus approprié). On utiliserait global Mutex
lors de la suppression du nœud d'une liste accessible globalement (dernière chose que vous voulez qu'un autre thread fasse quelque chose pendant que vous supprimez le nœud). Lorsque vous acquérez Mutex
si un thread différent essaie d'acquérir le même, Mutex
il sera mis en veille jusqu'à ce que le même thread qui l'a acquis le Mutex
libère.
Un bon exemple sur la création d'un mutex mondial est de @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 utilisez 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.
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 eux sont:
Les mutex ont un concept de 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 aucun concept de propriétaire. Tout processus peut déverrouiller un sémaphore.
Contrairement aux sémaphores, les mutex offrent une sécurité d'inversion prioritaire. Étant donné que le mutex connaît son propriétaire actuel, il est possible de promouvoir la priorité du propriétaire chaque fois qu'une tâche de priorité supérieure commence à attendre sur le mutex.
Les mutex offrent également une sécurité de suppression, où le processus contenant le mutex ne peut pas être accidentellement supprimé. Les sémaphores ne fournissent pas cela.
Ma compréhension est qu'un mutex est uniquement destiné à être utilisé dans un seul processus, mais sur ses nombreux threads, alors qu'un sémaphore peut être utilisé sur plusieurs processus et sur leurs ensembles de threads correspondants.
En outre, un mutex est binaire (il est verrouillé ou déverrouillé), tandis qu'un sémaphore a une notion de comptage ou une file d'attente de plusieurs demandes de verrouillage et déverrouillage.
Quelqu'un pourrait-il vérifier mon explication? Je parle dans le contexte de Linux, en particulier de Red Hat Enterprise Linux (RHEL) version 6, qui utilise le noyau 2.6.32.
Utilisation de la programmation C sur une variante Linux comme cas de base pour des exemples.
Fermer à clé:
• Habituellement, un binaire de construction très simple en fonctionnement verrouillé ou déverrouillé
• Aucun concept de propriété de thread, de priorité, de séquencement, etc.
• Habituellement, un verrou tournant où le fil vérifie en permanence la disponibilité des verrous.
• Se fonde généralement sur des opérations atomiques, par exemple, tester et définir, comparer et échanger, extraire et ajouter, etc.
• Nécessite généralement un support matériel pour le fonctionnement atomique.
Verrous de fichiers:
• Habituellement utilisé pour coordonner l'accès à un fichier via plusieurs processus.
• Plusieurs processus peuvent contenir le verrou de lecture. Cependant, lorsqu'un seul processus détient le verrou d'écriture, aucun autre processus n'est autorisé à acquérir un verrou de lecture ou d'écriture.
• Exemple: flock, fcntl etc.
Mutex:
• Les appels de fonction Mutex fonctionnent généralement dans l'espace du noyau et entraînent des appels système.
• Il utilise le concept de propriété. Seul le thread qui détient actuellement le mutex peut le déverrouiller.
• Mutex n'est pas récursif (exception: PTHREAD_MUTEX_RECURSIVE).
• Habituellement utilisé en association avec les variables de condition et transmis comme arguments à 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 forcément appliqué sur tous les systèmes.
Sémaphore:
• Il s'agit d'un entier maintenu par le noyau dont les valeurs ne doivent pas tomber en dessous de zéro.
• Il peut être utilisé pour synchroniser les processus.
• La valeur du sémaphore peut être définie sur une valeur supérieure à 1, auquel cas la valeur indique généralement le nombre de ressources disponibles.
• Un sémaphore dont la valeur est limitée à 1 et 0 est appelé sémaphore binaire.
Supporting ownership
, maximum number of processes share lock
Et maximum number of allowed processes/threads in critical section
sont trois principaux facteurs qui déterminent le nom / type de l'objet en même temps que le nom général de lock
. Étant donné que la valeur de ces facteurs est binaire (a deux états), nous pouvons les résumer dans un tableau de vérité 3 * 8.
X Y Z Name
--- --- --- ------------------------
0 ∞ ∞ Semaphore
0 ∞ 1 Binary Semaphore
0 1 ∞ SemaphoreSlim
0 1 1 Binary SemaphoreSlim(?)
1 ∞ ∞ Recursive-Mutex(?)
1 ∞ 1 Mutex
1 1 ∞ N/A(?)
1 1 1 Lock/Monitor
N'hésitez pas à éditer ou développer ce tableau, je l'ai posté en tant que tableau ascii pour être éditable :)