Un sémaphore est un concept de programmation fréquemment utilisé pour résoudre des problèmes de multi-threading. Ma question à la communauté:
Qu'est-ce qu'un sémaphore et comment l'utilisez-vous?
Un sémaphore est un concept de programmation fréquemment utilisé pour résoudre des problèmes de multi-threading. Ma question à la communauté:
Qu'est-ce qu'un sémaphore et comment l'utilisez-vous?
Réponses:
Considérez les sémaphores comme des videurs dans une boîte de nuit. Il y a un nombre dédié de personnes autorisées à la fois dans le club. Si le club est plein, personne n'est autorisé à entrer, mais dès qu'une personne quitte, une autre peut entrer.
C'est simplement un moyen de limiter le nombre de consommateurs pour une ressource spécifique. Par exemple, pour limiter le nombre d'appels simultanés à une base de données dans une application.
Voici un exemple très pédagogique en C # :-)
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);
}
}
}
L'article Mutexes and Semaphores Demystified by Michael Barr est une excellente introduction courte sur ce qui rend les mutexes et les sémaphores différents, et quand ils doivent et ne doivent pas être utilisés. J'ai extrait plusieurs paragraphes clés ici.
Le point clé est que les mutex devraient être utilisés pour protéger les ressources partagées, tandis que les sémaphores devraient être utilisés pour la signalisation. Vous ne devez généralement pas utiliser de sémaphores pour protéger les ressources partagées, ni de mutex pour la signalisation. Il y a des problèmes, par exemple, avec l'analogie du videur en termes d'utilisation de sémaphores pour protéger les ressources partagées - vous pouvez les utiliser de cette façon, mais cela peut rendre difficile le diagnostic des bogues.
Bien que les mutex et les sémaphores présentent certaines similitudes dans leur implémentation, ils doivent toujours être utilisés différemment.
La réponse la plus courante (mais néanmoins incorrecte) à la question posée en haut est que les mutex et les sémaphores sont très similaires, la seule différence significative étant que les sémaphores peuvent compter plus d'un. Presque tous les ingénieurs semblent bien comprendre qu'un mutex est un indicateur binaire utilisé pour protéger une ressource partagée en garantissant l'exclusion mutuelle dans les sections critiques du code. Mais lorsqu'on leur a demandé de développer la façon d'utiliser un «sémaphore de comptage», la plupart des ingénieurs - ne variant que dans leur degré de confiance - expriment une certaine idée de l'opinion du manuel selon laquelle ils sont utilisés pour protéger plusieurs ressources équivalentes.
...
À ce stade, une analogie intéressante est faite en utilisant l'idée des clés de la salle de bain comme protection des ressources partagées - la salle de bain. Si un magasin n'a qu'une seule salle de bain, une seule clé sera suffisante pour protéger cette ressource et empêcher plusieurs personnes de l'utiliser simultanément.
S'il y a plusieurs salles de bains, on pourrait être tenté de les saisir de la même manière et de créer plusieurs clés - cela ressemble à un sémaphore mal utilisé. Une fois que vous avez une clé, vous ne savez pas vraiment quelle salle de bain est disponible, et si vous suivez cette voie, vous finirez probablement par utiliser des mutex pour fournir ces informations et vous assurer de ne pas prendre une salle de bain déjà occupée. .
Un sémaphore n'est pas le bon outil pour protéger plusieurs des mêmes ressources, mais c'est le nombre de personnes qui y pensent et l'utilisent. L'analogie du videur est distinctement différente - il n'y a pas plusieurs du même type de ressource, au lieu de cela il y a une ressource qui peut accepter plusieurs utilisateurs simultanés. Je suppose qu'un sémaphore peut être utilisé dans de telles situations, mais il y a rarement des situations réelles où l'analogie tient réellement - c'est plus souvent qu'il y en a plusieurs du même type, mais toujours des ressources individuelles, comme les salles de bains, qui ne peuvent pas être utilisées par ici.
...
L'utilisation correcte d'un sémaphore est pour la signalisation d'une tâche à l'autre. Un mutex est censé être pris et libéré, toujours dans cet ordre, par chaque tâche qui utilise la ressource partagée qu'il protège. En revanche, les tâches qui utilisent des sémaphores signalent ou attendent, mais pas les deux. Par exemple, la tâche 1 peut contenir du code pour publier (c'est-à-dire, signaler ou incrémenter) un sémaphore particulier lorsque le bouton "power" est enfoncé et la tâche 2, qui réveille l'affichage, dépend de ce même sémaphore. Dans ce scénario, une tâche est le producteur du signal d'événement; l'autre le consommateur.
...
Ici, il est important de noter que les mutex interfèrent mal avec les systèmes d'exploitation en temps réel, provoquant une inversion de priorité où une tâche moins importante peut être exécutée avant une tâche plus importante en raison du partage des ressources. En bref, cela se produit lorsqu'une tâche de priorité inférieure utilise un mutex pour récupérer une ressource, A, puis tente de récupérer B, mais est interrompue car B n'est pas disponible. Pendant qu'il attend, une tâche de priorité plus élevée survient et nécessite A, mais elle est déjà bloquée, et par un processus qui n'est même pas en cours d'exécution car il attend B. Il existe de nombreuses façons de résoudre ce problème, mais le plus souvent il est corrigé en modifiant le mutex et le gestionnaire de tâches. Le mutex est beaucoup plus complexe dans ces cas qu'un sémaphore binaire,
...
La cause de la confusion moderne répandue entre les mutex et les sémaphores est historique, car elle remonte à l'invention de 1974 du sémaphore («S» majuscule, dans cet article) par Djikstra. Avant cette date, aucun des mécanismes de synchronisation et de signalisation des tâches à sécurité intrinsèque connus des informaticiens n'était évolutif de manière efficace pour être utilisé par plus de deux tâches. Le sémaphore révolutionnaire, sûr et évolutif de Dijkstra a été appliqué à la fois à la protection des sections critiques et à la signalisation. Et c'est ainsi que la confusion a commencé.
Cependant, il est devenu plus tard évident pour les développeurs de systèmes d'exploitation, après l'apparition du RTOS préemptif basé sur la priorité (par exemple, VRTX, ca. 1980), la publication d'articles académiques établissant le RMA et les problèmes causés par l'inversion de priorité, et un article sur la priorité protocoles d'héritage en 1990 3, il est devenu évident que les mutex doivent être plus que de simples sémaphores avec un compteur binaire.
Mutex: partage de ressources
Sémaphore: signalisation
N'utilisez pas l'un pour l'autre sans avoir soigneusement pris en compte les effets secondaires.
Mutex: accès exclusif des membres à une ressource
Sémaphore: accès de n membres à une ressource
Autrement dit, un mutex peut être utilisé pour synchroniser l'accès à un compteur, un fichier, une base de données, etc.
Un sempahore peut faire la même chose mais prend en charge un nombre fixe d'appels simultanés. Par exemple, je peux encapsuler mes appels de base de données dans un sémaphore (3) afin que mon application multithread atteigne la base de données avec au plus 3 connexions simultanées. Toutes les tentatives seront bloquées jusqu'à ce que l'un des trois emplacements s'ouvre. Ils font des choses comme faire de la limitation naïve vraiment, vraiment facile.
Considérez, un taxi qui peut accueillir un total de 3 ( arrière ) +2 ( avant ) personnes, y compris le chauffeur. Ainsi, un semaphore
autorise seulement 5 personnes à l'intérieur d'une voiture à la fois. Et un mutex
permet une seule personne sur un seul siège de la voiture.
Par conséquent, Mutex
est d'autoriser l'accès exclusif à une ressource ( comme un thread OS ) tandis que a Semaphore
est d'autoriser l'accès à n nombre de ressources à la fois.
@Craig:
Un sémaphore est un moyen de verrouiller une ressource afin qu'il soit garanti que pendant l'exécution d'un morceau de code, seul ce morceau de code a accès à cette ressource. Cela empêche deux threads d'accéder simultanément à une ressource, ce qui peut provoquer des problèmes.
Ce n'est pas limité à un seul thread. Un sémaphore peut être configuré pour permettre à un nombre fixe de threads d'accéder à une ressource.
Le sémaphore peut également être utilisé comme ... sémaphore. Par exemple, si vous avez plusieurs processus de mise en file d'attente de données dans une file d'attente et une seule tâche consommant des données de la file d'attente. Si vous ne voulez pas que votre tâche consommatrice interroge constamment la file d'attente pour les données disponibles, vous pouvez utiliser le sémaphore.
Ici, le sémaphore n'est pas utilisé comme mécanisme d'exclusion, mais comme mécanisme de signalisation. La tâche consommatrice attend sur le sémaphore La tâche productrice est en cours de publication sur le sémaphore.
De cette façon, la tâche consommatrice s'exécute quand et seulement lorsqu'il y a des données à retirer de la file d'attente
Il existe deux concepts essentiels à la création de programmes simultanés: la synchronisation et l'exclusion mutuelle. Nous verrons comment ces deux types de verrous (les sémaphores sont plus généralement une sorte de mécanisme de verrouillage) nous aident à réaliser la synchronisation et l'exclusion mutuelle.
Un sémaphore est une construction de programmation qui nous aide à atteindre la concurrence, en implémentant à la fois la synchronisation et l'exclusion mutuelle. Les sémaphores sont de deux types, binaire et comptage.
Un sémaphore se compose de deux parties: un compteur et une liste de tâches en attente d'accès à une ressource particulière. Un sémaphore effectue deux opérations: attendre (P) [c'est comme acquérir un verrou], et relâcher (V) [similaire à libérer un verrou] - ce sont les deux seules opérations que l'on peut effectuer sur un sémaphore. Dans un sémaphore binaire, le compteur va logiquement entre 0 et 1. Vous pouvez le considérer comme étant similaire à un verrou avec deux valeurs: ouvert / fermé. Un sémaphore de comptage a plusieurs valeurs pour le comptage.
Ce qui est important à comprendre, c'est que le compteur de sémaphores garde une trace du nombre de tâches qui n'ont pas à bloquer, c'est-à-dire qu'elles peuvent progresser. Les tâches se bloquent et ne s'ajoutent à la liste du sémaphore que lorsque le compteur est nul. Par conséquent, une tâche est ajoutée à la liste dans la routine P () si elle ne peut pas progresser et "libérée" à l'aide de la routine V ().
Maintenant, il est assez évident de voir comment les sémaphores binaires peuvent être utilisés pour résoudre la synchronisation et l'exclusion mutuelle - ce sont essentiellement des verrous.
ex. Synchronisation:
thread A{
semaphore &s; //locks/semaphores are passed by reference! think about why this is so.
A(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
;// some block of code B2
...
}
//thread B{
semaphore &s;
B(semaphore &s): s(s){} //constructor
foo(){
...
...
// some block of code B1
s.V();
..
}
main(){
semaphore s(0); // we start the semaphore at 0 (closed)
A a(s);
B b(s);
}
Dans l'exemple ci-dessus, B2 ne peut s'exécuter qu'une fois que B1 a terminé son exécution. Disons que le thread A vient s'exécute en premier - arrive à sem.P (), et attend, puisque le compteur est 0 (fermé). Le fil B arrive, termine B1, puis libère le fil A - qui termine ensuite B2. Nous réalisons donc la synchronisation.
Examinons maintenant l'exclusion mutuelle avec un sémaphore binaire:
thread mutual_ex{
semaphore &s;
mutual_ex(semaphore &s): s(s){} //constructor
foo(){
...
s.P();
//critical section
s.V();
...
...
s.P();
//critical section
s.V();
...
}
main(){
semaphore s(1);
mutual_ex m1(s);
mutual_ex m2(s);
}
L'exclusion mutuelle est également assez simple - m1 et m2 ne peuvent pas entrer en même temps dans la section critique. Ainsi, chaque thread utilise le même sémaphore pour fournir une exclusion mutuelle pour ses deux sections critiques. Maintenant, est-il possible d'avoir une plus grande simultanéité? Dépend des sections critiques. (Réfléchissez à la façon dont on pourrait utiliser les sémaphores pour parvenir à l'exclusion mutuelle. Indice indice: ai-je nécessairement seulement besoin d'utiliser un seul sémaphore?)
Sémaphore de comptage: un sémaphore avec plus d'une valeur. Voyons ce que cela implique - un verrou avec plus d'une valeur ?? Donc ouvert, fermé et ... hmm. À quoi sert un verrouillage à plusieurs étages dans l'exclusion mutuelle ou la synchronisation?
Prenons le plus simple des deux:
Synchronisation à l'aide d'un sémaphore de comptage: Disons que vous avez 3 tâches - n ° 1 et 2 que vous souhaitez exécuter après 3. Comment concevriez-vous votre synchronisation?
thread t1{
...
s.P();
//block of code B1
thread t2{
...
s.P();
//block of code B2
thread t3{
...
//block of code B3
s.V();
s.V();
}
Donc, si votre sémaphore commence fermé, vous vous assurez que les blocs t1 et t2 soient ajoutés à la liste des sémaphores. Vient ensuite tout le t3 important, termine son activité et libère le t1 et le t2. Dans quel ordre sont-ils libérés? Dépend de l'implémentation de la liste des sémaphores. Pourrait être FIFO, pourrait être basé sur une priorité particulière, etc. (Remarque: réfléchissez à la façon dont vous organiseriez vos P et V; si vous vouliez que t1 et t2 soient exécutés dans un ordre particulier et si vous n'étiez pas au courant de l'implémentation du sémaphore)
(Découvrez: que se passe-t-il si le nombre de V est supérieur au nombre de P?)
Exclusion mutuelle à l'aide de sémaphores de comptage: j'aimerais que vous construisiez votre propre pseudocode pour cela (vous fait mieux comprendre les choses!) - mais le concept fondamental est le suivant: un sémaphore de comptage de compteur = N permet à N tâches d'entrer librement dans la section critique . Cela signifie que vous avez N tâches (ou threads, si vous le souhaitez) entrez dans la section critique, mais la tâche N + 1 est bloquée (va sur notre liste de tâches bloquées préférée), et n'est autorisée que lorsque quelqu'un V est le sémaphore au moins une fois. Ainsi, le compteur de sémaphores, au lieu de basculer entre 0 et 1, va maintenant entre 0 et N, permettant à N tâches d'entrer et de sortir librement, ne bloquant personne!
Maintenant mon Dieu, pourquoi auriez-vous besoin d'une chose aussi stupide? N'est-ce pas tout l'intérêt de l'exclusion mutuelle de ne pas laisser plus d'un mec accéder à une ressource ?? (Astuce Astuce ... Vous n'avez pas toujours qu'un seul lecteur dans votre ordinateur, n'est-ce pas ...?)
Réfléchir : L'exclusion mutuelle est-elle obtenue en ayant un sémaphore de comptage seul? Que se passe-t-il si vous disposez de 10 instances d'une ressource et que 10 threads entrent (via le sémaphore de comptage) et tentent d'utiliser la première instance?
Un sémaphore est un objet contenant un nombre naturel (c'est-à-dire un entier supérieur ou égal à zéro) sur lequel deux opérations de modification sont définies. Une opération, V
ajoute 1 au naturel. L'autre opération P
,, diminue le nombre naturel de 1. Les deux activités sont atomiques (c'est-à-dire qu'aucune autre opération ne peut être exécutée en même temps que a V
ou a P
).
Parce que le nombre naturel 0 ne peut pas être diminué, l'appel P
à un sémaphore contenant un 0 bloquera l'exécution du processus appelant (/ thread) jusqu'à un certain moment où le nombre n'est plus 0 et P
peut être exécuté avec succès (et atomiquement).
Comme mentionné dans d'autres réponses, les sémaphores peuvent être utilisés pour restreindre l'accès à une certaine ressource à un nombre maximum (mais variable) de processus.
J'ai créé la visualisation qui devrait aider à comprendre l'idée. Le sémaphore contrôle l'accès à une ressource commune dans un environnement multithreading.
ExecutorService executor = Executors.newFixedThreadPool(7);
Semaphore semaphore = new Semaphore(4);
Runnable longRunningTask = () -> {
boolean permit = false;
try {
permit = semaphore.tryAcquire(1, TimeUnit.SECONDS);
if (permit) {
System.out.println("Semaphore acquired");
Thread.sleep(5);
} else {
System.out.println("Could not acquire semaphore");
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
} finally {
if (permit) {
semaphore.release();
}
}
};
// execute tasks
for (int j = 0; j < 10; j++) {
executor.submit(longRunningTask);
}
executor.shutdown();
Production
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Exemple de code de l' article
Un indicateur matériel ou logiciel. Dans les systèmes multitâches, un sémaphore est une variable avec une valeur qui indique l'état d'une ressource commune. Un processus qui a besoin de la ressource vérifie le sémaphore pour déterminer l'état des ressources, puis décide de la marche à suivre.
Les sémaphores agissent comme des limiteurs de threads.
Exemple: si vous disposez d'un pool de 100 threads et que vous souhaitez effectuer une opération de base de données. Si 100 threads accèdent à la base de données à un moment donné, il peut y avoir un problème de verrouillage dans la base de données afin que nous puissions utiliser un sémaphore qui n'autorise qu'un nombre limité de threads à la fois. L'exemple ci-dessous autorise un seul thread à la fois. Lorsqu'un thread appelle leacquire()
méthode, il obtient alors l'accès et après avoir appelé lerelease()
méthode, il libérera l'accès afin que le thread suivant obtienne l'accès.
package practice;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore s = new Semaphore(1);
semaphoreTask s1 = new semaphoreTask(s);
semaphoreTask s2 = new semaphoreTask(s);
semaphoreTask s3 = new semaphoreTask(s);
semaphoreTask s4 = new semaphoreTask(s);
semaphoreTask s5 = new semaphoreTask(s);
s1.start();
s2.start();
s3.start();
s4.start();
s5.start();
}
}
class semaphoreTask extends Thread {
Semaphore s;
public semaphoreTask(Semaphore s) {
this.s = s;
}
@Override
public void run() {
try {
s.acquire();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" Going to perform some operation");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Imaginez donc que tout le monde essaie d'aller aux toilettes et qu'il n'y ait qu'un certain nombre de clés pour la salle de bain. S'il ne reste plus assez de clés, cette personne doit attendre. Considérez donc le sémaphore comme représentant l'ensemble de clés disponibles pour les salles de bains (les ressources du système) auxquelles différents processus (les amateurs de salles de bains) peuvent demander l'accès.
Imaginez maintenant deux processus essayant d'aller aux toilettes en même temps. Ce n'est pas une bonne situation et des sémaphores sont utilisés pour empêcher cela. Malheureusement, le sémaphore est un mécanisme volontaire et les processus (nos amateurs de salle de bain) peuvent l'ignorer (c'est-à-dire que même s'il y a des clés, quelqu'un peut toujours ouvrir la porte).
Il existe également des différences entre les sémaphores binaires / mutex et de comptage.
Consultez les notes de cours sur http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html .
C'est une vieille question mais l'une des utilisations les plus intéressantes du sémaphore est un verrou en lecture / écriture et il n'a pas été explicitement mentionné.
Les verrous r / w fonctionnent de façon simple: consommez un permis pour un lecteur et tous les permis pour des écrivains. En effet, une implémentation triviale du verrou ar / w mais nécessite une modification des métadonnées à la lecture (en fait deux fois) qui peut devenir un goulot d'étranglement, encore nettement mieux qu'un mutex ou un verrou.
Un autre inconvénient est que les écrivains peuvent également être démarrés assez facilement à moins que le sémaphore soit juste ou que les écritures n'obtiennent des permis dans plusieurs demandes, dans ce cas, elles ont besoin d'un mutex explicite entre elles.
En outre lecture :
Un sémaphore est un moyen de verrouiller une ressource afin qu'il soit garanti que pendant l'exécution d'un morceau de code, seul ce morceau de code a accès à cette ressource. Cela empêche deux threads d'accéder simultanément à une ressource, ce qui peut provoquer des problèmes.