web-dev-qa-db-fra.com

Lock, mutex, sémaphore ... quelle est la différence?

J'ai entendu ces mots liés à la programmation simultanée, mais quelle est la différence entre eux?

372
victor

Un verrou n'autorise qu'un seul thread à entrer dans la pièce verrouillée et le verrou n'est partagé avec aucun autre processus.

Un mutex est identique à un verrou, mais il peut être étendu au système (partagé par plusieurs processus).

Un sémaphore fait la même chose qu'un mutex mais autorise x nombre de threads à entrer. Ceci peut être utilisé par exemple pour limiter le nombre de tâches intensives en CPU, io ou en RAM exécutées simultanément.

Pour un article plus détaillé sur les différences entre mutex et sémaphore, lisez ici .

Vous disposez également de verrous en lecture/écriture permettant un nombre illimité de lecteurs ou 1 rédacteur à la fois.

463
Peter

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 de seulement un actif thread de beaucoup d'autres dans un processus Les autres threads non sélectionnés (@ acquérant cet objet) sont placés dans sleep .

[Pas de capacité entre processus, objet très primitif].

2) Sémaphore de Mutex (aka Mutex) = Objet du noyau utilisé pour permettre l'exécution de just un fil actif parmi beaucoup d'autres, entre différents processus . Les autres threads non sélectionnés (@ acquérant cet objet) sont placés dans sleep . Cet objet prend en charge la propriété de fil, la notification de fin de fil, la récursivité (plusieurs appels "acquis" du même fil) et "l'évitement d'inversion de priorité".

[Capacité inter-processus, très sûre à utiliser, une sorte d'objet de synchronisation 'de haut niveau'].

3) Counting Semaphore (aka Semaphore) = Objet du noyau utilisé pour permettre l'exécution de un groupe de threads actifs parmi tant d’autres. Les autres threads non sélectionnés (@ acquérant cet objet) sont placés dans sleep .

[La capacité interprocess est cependant peu sûre à utiliser car elle manque des attributs 'mutex' suivants: notification de fin de thread, récursivité ?, 'évitement de priorité inversion' ?, etc].

4) Et maintenant, parlons de "spinlocks", d'abord quelques définitions:

Région critique = Une région de mémoire partagée par 2 processus ou plus.

Lock = Une variable dont la valeur permet ou interdit l'entrée d'une "région critique". (Il pourrait être implémenté comme un simple "drapeau booléen").

Occupé en attente = Test continu d'une variable jusqu'à ce qu'une valeur apparaisse.

Finalement:

Spin-lock (aka Spinlock) = A verrouille qui utilise en attente . (L’acquisition du verrou est effectuée par xchg ou similaire opérations atomiques ).

[Pas de thread en veille, principalement utilisé au niveau du noyau. Ineffcient pour le code de niveau utilisateur].

Comme dernier commentaire, je ne suis pas sûr mais je peux vous parier que beaucoup des 3 premiers objets de synchronisation ci-dessus (n ° 1, n ° 2 et n ° 3) utilisent cette simple bête (n ° 4) dans le cadre de leur implémentation.

Bonne journée!.

Références:

- Concepts temps réel pour les systèmes embarqués de Qing Li avec Caroline Yao (CMP Books).

- Systèmes d'exploitation modernes (3ème) par Andrew Tanenbaum (Pearson Education International).

-Programmer des applications pour Microsoft Windows (4ème) par Jeffrey Richter (Microsoft Programming Series).

Vous pouvez également consulter: https://stackoverflow.com/a/24586803/3163691

100
fante

La plupart des problèmes peuvent être résolus en utilisant (i) seulement des verrous, (ii) juste des sémaphores, ..., ou (iii) une combinaison des deux! Comme vous l'avez peut-être découvert, elles sont très similaires: les deux empêchent conditions de concurrence , les deux ont des opérations acquire()/release(), les deux ne provoquant aucun ou plusieurs threads. pour être bloqué/soupçonné ... Vraiment, la différence cruciale réside uniquement sur comment verrouiller et déverrouiller .

  • Un verrou (ou mutex ) a deux états (0 ou 1) . Il peut être soit déverrouillé ou verrouillé . Ils sont souvent utilisés pour s'assurer qu'un seul thread entre dans une section critique à la fois.
  • Un sémaphore a plusieurs é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 garantir qu’un seul thread entre dans une section critique précisément lorsque le nombre d’unités d’une ressource a atteint ou n’a pas atteint une valeur particulière (soit en décomptant jusqu'à cette valeur, soit en comptant jusqu'à cette valeur. ).

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 d’acquérir le verrou sont à l’état 1 ont abouti. Pour les sémaphores - les tentatives d'acquisition du verrou dans les états {1, 2, 3, ...} aboutissent.

Pour les verrous à l'état d'état 0, si le même thread qui s'appelait précédemment acquire() appelle maintenant release, la libération est réussie. Si un thread différent a essayé cela - il incombe à l'implémentation/à la bibliothèque de savoir ce qui se passe (généralement, la tentative est ignorée ou une erreur est générée). Pour les sémaphores à l'état 0, n'importe quel thread peut appeler release et le processus aboutira (quel que soit le thread précédent utilisé pour acquérir le sémaphore à l'état 0).

La discussion précédente montre que les verrous ont la notion de propriétaire (le seul fil pouvant appeler release est le propriétaire), alors que les sémaphores n’ont pas de propriétaire (aucun fil ne peut appelez release sur un sémaphore).


Ce qui cause beaucoup de confusion est qu’en pratique, ils sont de nombreuses variantes de cette définition de haut niveau.

Variations importantes à prendre en compte :

  • Comment appeler la acquire()/release()? - [Varie massivement ]
  • Votre verrou/sémaphore utilise-t-il une "file d'attente" ou un "ensemble" pour se souvenir des threads en attente?
  • Est-ce que votre verrou/sémaphore peut être partagé avec les threads d'autres processus?
  • Votre cadenas est-il "réentrant"? - [Habituellement, oui].
  • Est-ce que votre verrou est "bloquant/non bloquant"? - [Normalement, les verrous non bloquants sont utilisés comme verrous bloquants (ou spin-locks) provoquant une attente en attente].
  • Comment vous assurez-vous que les opérations sont "atomiques"?

Cela dépend de votre livre/conférencier/langue/bibliothèque/environnement.
Voici un rapide aperçu de la manière dont 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 pas être partagés avec d'autres processus (PTHREAD_PROCESS_PRIVATE), mais les mutex ont un attribut appelé pshared . Lorsque cette option est définie, le mutex est partagé entre les processus (PTHREAD_PROCESS_SHARED).
  • Un verrou est la même chose qu'un mutex.
  • Un sémaphore est implémenté via sem_t. Semblables aux mutex, les sémaphores peuvent être partagés entre les threasds de nombreux processus ou gardés privés des threads d'un seul processus. Cela dépend de l'argument pshared fourni à sem_init.

python ( threading.py )

  • Un verrou (threading.RLock) est généralement identique à C/C++ pthread_mutex_ts. Les deux sont tous deux réentrants . Cela signifie qu'ils ne peuvent être déverrouillés que par le même thread qui l'a verrouillé. Les sémaphores sem_t, les sémaphores threading.Semaphore et les verrous theading.Lock ne sont pas réentrants - car c'est le cas tout thread peut déverrouiller le sémaphore.
  • Un mutex est identique à un verrou (le terme n'est pas souvent utilisé en python).
  • Un sémaphore (threading.Semaphore) est généralement identique à sem_t. Bien qu'avec sem_t, une file d'id de threads est utilisée pour rappeler l'ordre dans lequel les threads sont devenus bloqués lors d'une tentative de verrouillage alors qu'il est verrouillé. Lorsqu'un thread déverrouille un sémaphore, le premier thread de la file d'attente (s'il en existe un) est choisi comme 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. Par conséquent, l'ordre dans lequel les threads ont été bloqués n'est pas stocké - les threads de l'ensemble peuvent être choisis comme suit: soyez le prochain propriétaire.

Java ( Java.util.concurrent )

  • Un verrou (Java.util.concurrent.ReentrantLock) est généralement identique à C/C++ pthread_mutex_t 'et au threading.RLock de C/C++ il implémente également un verrou réentrant. Le partage des verrous entre processus est plus difficile dans Java, car la machine virtuelle Java joue le rôle d'intermédiaire. Si un thread tente de déverrouiller un verrou qu'il ne possède pas, un IllegalMonitorStateException est lancé.
  • Un mutex est identique à un verrou (le terme n'est pas souvent utilisé en Java).
  • Un sémaphore (Java.util.concurrent.Semaphore) est généralement identique à sem_t et threading.Semaphore. Le constructeur des sémaphores Java accepte un paramètre booléen d'équité qui permet de définir s'il faut utiliser un ensemble (false) ou une file d'attente (true) pour stocker les threads en attente.

En théorie, les sémaphores sont souvent discutés, mais en pratique, les sémaphores sont moins utilisés. Un sémaphore ne contient que l’état de un entier, il est donc souvent assez inflexible et qu’il en faut plusieurs à la fois, ce qui entraîne des difficultés pour comprendre le 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 de niveau supérieur plus orientées objet telles que "variables de condition" et "moniteurs" sont utilisées à la place.

23
James Lawson

Jetez un oeil à Tutoriel de multithreading par John Kopplin.

Dans la section Synchronisation entre les threads , il explique les différences entre les événements, verrou, mutex, sémaphore, délai d'attente

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 processus unique.

Une autre différence entre un mutex et une section critique est la suivante: L'objet de section appartient actuellement à un autre thread, EnterCriticalSection() attend indéfiniment la propriété alors que WaitForSingleObject(), utilisé avec un mutex, vous permet de spécifier un délai d'expiration.

Un sémaphore conserve un nombre compris entre zéro et une valeur maximale, ce qui limite le nombre de threads accédant simultanément à une ressource partagée.

22
onmyway133

Je vais essayer de couvrir avec des exemples:

Verrouiller: Un exemple d'utilisation de lock serait un dictionnaire partagé dans lequel des éléments (qui doivent avoir des clés uniques) sont ajoutés.
Le verrou ferait en sorte qu’un thread n’entre pas dans le mécanisme de code qui vérifie la présence d’élément 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 tente d'entrer un code verrouillé, il attendra (soit bloqué) 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 avez un pool de connexions, puis qu'un seul thread pourrait 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 l'un des 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 s'agit plutôt de Semaphore(1,1) et il est souvent utilisé dans le monde entier (sinon, on peut dire que lock est plus approprié). On utiliserait global Mutex lors de la suppression d'un nœud d'une liste accessible globalement (dernière chose pour laquelle un autre thread doit faire quelque chose pendant la suppression du nœud). Lorsque vous acquérez Mutex si un autre thread tente d’acquérir le même Mutex, il sera mis en veille jusqu’à ce que le MÊME thread qui a acquis le Mutex le relâche.

Le bon exemple sur la création d'un mutex global est donné 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 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 fait gagner du temps.

14
Matas Vaitkevicius

Wikipedia a une grande section sur le 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 pas de concept de propriétaire. Tout processus peut déverrouiller un sémaphore.

Contrairement aux sémaphores, les mutex fournissent une sécurité d'inversion de priorité. Comme 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 fournissent également une sécurité de suppression, dans laquelle le processus contenant le mutex ne peut pas être supprimé accidentellement. Les sémaphores ne fournissent pas cela.

7
andy boot

Si j'ai bien compris, un mutex ne peut être utilisé que dans un seul processus, mais sur plusieurs de ses 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é), alors qu'un sémaphore a une notion de comptage ou une file d'attente de plusieurs requêtes de verrouillage et de déverrouillage.

Quelqu'un pourrait-il vérifier mon explication? Je parle dans le contexte de Linux, en particulier de la version 6 de Red Hat Enterprise Linux (RHEL), qui utilise le noyau 2.6.32.

5
Bruce Penswick

Utilisation de la programmation C sur une variante de Linux comme exemple de base.

Lock:

• Généralement, une construction très simple en mode binaire est verrouillée ou déverrouillée

• Pas de concept de propriété de fil, de priorité, de séquence, etc.

• Généralement, un verrou de rotation où le thread vérifie en permanence la disponibilité des verrous.

• repose généralement sur des opérations atomiques, par exemple Tester et définir, comparer et permuter, récupérer et ajouter, etc.

• Nécessite généralement un support matériel pour le fonctionnement atomique.

File Locks:

• Habituellement utilisé pour coordonner l'accès à un fichier via plusieurs processus.

• Plusieurs processus peuvent conserver le verrou en lecture. Toutefois, lorsqu'un processus unique détient le verrou en écriture, aucun autre processus n'est autorisé à acquérir un verrou en lecture ou en écriture.

• Exemple: flock, fcntl, etc.

Mutex:

• Les appels de fonctions mutex fonctionnent généralement dans l’espace noyau et donnent lieu à des appels système.

• Il utilise le concept de propriété. Seul le fil qui détient actuellement le mutex peut le déverrouiller.

• Le mutex n'est pas récursif (exception: PTHREAD_MUTEX_RECURSIVE).

• Habituellement utilisé en association avec des variables de condition et transmis en tant qu'arguments, par exemple. pthread_cond_signal, pthread_cond_wait etc.

• Certains systèmes UNIX autorisent l'utilisation de mutex par plusieurs processus, bien que cela puisse ne pas être appliqué sur tous les systèmes.

Sémaphore:

• Il s'agit d'un entier maintenu par le noyau dont les valeurs ne sont pas autorisées à descendre 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.

2
Judayle Dsouza