Quelle est la différence entre mutex et section critique?


134

Veuillez expliquer du point de vue Linux, Windows?

Je programme en C #, ces deux termes feraient-ils une différence? S'il vous plaît poster autant que vous le pouvez, avec des exemples et autres ....

Merci

Réponses:


232

Pour Windows, les sections critiques sont plus légères que les mutex.

Les mutex peuvent être partagés entre les processus, mais entraînent toujours un appel système au noyau, ce qui entraîne une surcharge.

Les sections critiques ne peuvent être utilisées que dans un processus, mais ont l'avantage de ne passer en mode noyau qu'en cas de conflit - Les acquisitions non contrôlées, qui devraient être le cas courant, sont incroyablement rapides. En cas de conflit, ils entrent dans le noyau pour attendre une primitive de synchronisation (comme un événement ou un sémaphore).

J'ai écrit un exemple d'application rapide qui compare le temps entre les deux. Sur mon système pour 1 000 000 d'acquisitions et de versions non contrôlées, un mutex prend plus d'une seconde. Une section critique prend environ 50 ms pour 1 000 000 d'acquisitions.

Voici le code de test, j'ai exécuté ceci et j'ai obtenu des résultats similaires si mutex est premier ou deuxième, donc nous ne voyons aucun autre effet.

HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);

1
Je ne sais pas si cela se rapporte ou non (puisque je n'ai pas compilé et essayé votre code), mais j'ai trouvé que l'appel de WaitForSingleObject avec INFINITE entraîne de mauvaises performances. Lui passer une valeur de délai d'expiration de 1 puis boucler tout en vérifiant son retour a fait une énorme différence dans les performances de certains de mes codes. C'est surtout dans le contexte de l'attente d'un handle de processus externe, cependant ... Pas un mutex. YMMV. Je serais intéressé de voir comment le mutex fonctionne avec cette modification. Le décalage horaire résultant de ce test semble plus important que prévu.
Troy Howard

5
@TroyHoward n'êtes-vous pas fondamentalement juste le verrouillage de rotation à ce stade?
dss539

Les raisons de cette distinction sont probablement principalement historiques. Il n'est pas difficile d'implémenter un verrouillage aussi rapide que CriticalSection dans le cas incontrôlé (peu d'instructions atomiques, pas d'appels système), mais qui fonctionne à travers les processus (avec un morceau de mémoire partagée). Voir par exemple les futex Linux .
regnarg

2
@TroyHoward essayez de forcer votre CPU à fonctionner à 100% tout le temps et voyez si INFINITE fonctionne mieux. La stratégie d'alimentation peut prendre jusqu'à 40 ms sur ma machine (Dell XPS-8700) pour revenir à pleine vitesse après avoir décidé de ralentir, ce qu'elle peut ne pas faire si vous dormez ou attendez seulement une milliseconde.
Stevens Miller

Je ne suis pas sûr de comprendre ce qui est démontré ici. Généralement, entrer dans une section critique nécessite d'acquérir une sorte de sémaphore. Êtes-vous en train de dire que dans les coulisses, l'O / S a un moyen efficace d'implémenter ce comportement de section critique sans nécessiter de mutex?
SN

89

D'un point de vue théorique, une section critique est un morceau de code qui ne doit pas être exécuté par plusieurs threads à la fois car le code accède aux ressources partagées.

Un mutex est un algorithme (et parfois le nom d'une structure de données) utilisé pour protéger les sections critiques.

Les sémaphores et les moniteurs sont des implémentations courantes d'un mutex.

En pratique, de nombreuses implémentations de mutex sont disponibles dans Windows. Ils diffèrent principalement en raison de leur mise en œuvre par leur niveau de verrouillage, leurs portées, leurs coûts et leurs performances sous différents niveaux de contention. Voir CLR Inside Out - Utilisation de la concurrence pour l'évolutivité pour un graphique des coûts des différentes implémentations de mutex.

Primitives de synchronisation disponibles.

L' lock(object)instruction est implémentée à l'aide d'un Monitor- voir MSDN pour référence.

Ces dernières années, de nombreuses recherches ont été effectuées sur la synchronisation non bloquante . L'objectif est de mettre en œuvre des algorithmes de manière sans verrouillage ou sans attente. Dans de tels algorithmes, un processus aide d'autres processus à terminer leur travail afin que le processus puisse enfin terminer son travail. En conséquence, un processus peut terminer son travail même lorsque d'autres processus, qui ont essayé d'effectuer un certain travail, se bloquent. En utilisant des verrous, ils ne libéreraient pas leurs verrous et empêcheraient d'autres processus de se poursuivre.


En voyant la réponse acceptée, je pensais que je me souvenais peut-être mal du concept des sections critiques, jusqu'à ce que je voie la perspective théorique que vous avez écrite. :)
Anirudh Ramanathan

2
La programmation pratique sans verrouillage est comme Shangri La, sauf qu'elle existe. L' article de Keir Fraser (PDF) explore cela de manière assez intéressante (remontant à 2004). Et nous avons encore du mal avec ça en 2012. Nous sommes nul.
Tim Post

22

En plus des autres réponses, les détails suivants sont spécifiques aux sections critiques sur Windows:

  • en l'absence de contestation, l'acquisition d'une section critique est aussi simple qu'une InterlockedCompareExchangeopération
  • la structure de la section critique contient de la place pour un mutex. Il est initialement non alloué
  • s'il y a conflit entre les threads pour une section critique, le mutex sera alloué et utilisé. La performance de la section critique se dégradera à celle du mutex
  • si vous prévoyez un conflit élevé, vous pouvez allouer la section critique en spécifiant un nombre de spin.
  • s'il y a contention sur une section critique avec un nombre de tours, le thread essayant d'acquérir la section critique tournera (occupé-attente) pendant ce nombre de cycles de processeur. Cela peut entraîner de meilleures performances que la mise en veille, car le nombre de cycles pour effectuer un changement de contexte vers un autre thread peut être beaucoup plus élevé que le nombre de cycles pris par le thread propriétaire pour libérer le mutex
  • si le nombre de tours expire, le mutex sera alloué
  • lorsque le thread propriétaire libère la section critique, il est nécessaire de vérifier si le mutex est alloué, si c'est le cas, il définira le mutex pour libérer un thread en attente

Sous Linux, je pense qu'ils ont un "verrouillage de rotation" qui sert un objectif similaire à la section critique avec un nombre de tours.


Malheureusement, une section critique de Windows implique d'effectuer une opération CAS en mode noyau , ce qui est massivement plus cher que l'opération interverrouillée réelle. En outre, les sections critiques de Windows peuvent être associées à un nombre de tours.
Promit le

2
Ce n'est certainement pas vrai. CAS peut être fait avec cmpxchg en mode utilisateur.
Michael

Je pensais que le nombre de tours par défaut était de zéro si vous appeliez InitializeCriticalSection - vous devez appeler InitializeCriticalSectionAndSpinCount si vous souhaitez appliquer un nombre de tours. Avez-vous une référence pour cela?
1800 INFORMATION

18

Critical Section et Mutex ne sont pas spécifiques au système d'exploitation, leurs concepts de multithreading / multiprocessing.

Section critique Est un morceau de code qui ne doit s'exécuter que par lui-même à un moment donné (par exemple, il y a 5 threads en cours d'exécution simultanément et une fonction appelée "critical_section_function" qui met à jour un tableau ... vous ne voulez pas les 5 threads mise à jour du tableau à la fois. Ainsi, lorsque le programme exécute la fonction critique_section_function (), aucun des autres threads ne doit exécuter sa fonction critique_section_function.

mutex * Mutex est un moyen d'implémenter le code de la section critique (pensez-y comme un jeton ... le thread doit en avoir la possession pour exécuter le critique_section_code)


2
En outre, les mutex peuvent être partagés entre les processus.
configurateur

14

Un mutex est un objet qu'un thread peut acquérir, empêchant d'autres threads de l'acquérir. Il est consultatif et non obligatoire; un thread peut utiliser la ressource représentée par le mutex sans l'acquérir.

Une section critique est une longueur de code garantie par le système d'exploitation pour ne pas être interrompue. En pseudo-code, ce serait comme:

StartCriticalSection();
    DoSomethingImportant();
    DoSomeOtherImportantThing();
EndCriticalSection();

1
Je pense que l'affiche parlait de primitives de synchronisation en mode utilisateur, comme un objet win32 Critical section, qui fournit simplement une exclusion mutuelle. Je ne sais pas pour Linux, mais le noyau Windows a des régions critiques qui se comportent comme vous le décrivez - non interruptibles.
Michael

1
Je ne sais pas pourquoi vous avez été critiqué. Il y a le concept d'une section critique, que vous avez correctement décrite, qui est différente de l'objet noyau Windows appelé CriticalSection, qui est un type de mutex. Je crois que le PO posait des questions sur cette dernière définition.
Adam Rosenfield

Au moins, j'ai été confus par l'étiquette indépendante de la langue. Mais dans tous les cas, c'est ce que nous obtenons pour Microsoft en nommant leur implémentation de la même manière que leur classe de base. Mauvaise pratique de codage!
Mikko Rantanen

Eh bien, il a demandé le plus de détails possible, et a spécifiquement dit que Windows et Linux semblaient que les concepts étaient bons. +1 - n'a pas compris le -1 non plus: /
Jason Coco

14

L'égalité Windows «rapide» de la sélection critique sous Linux serait un futex , qui signifie mutex d'espace utilisateur rapide. La différence entre un futex et un mutex est qu'avec un futex, le noyau n'est impliqué que lorsqu'un arbitrage est requis, donc vous économisez la surcharge de parler au noyau chaque fois que le compteur atomique est modifié. Ce .. peut enregistrer une importante quantité de verrous de négociation de temps dans certaines applications.

Un futex peut également être partagé entre les processus, en utilisant les moyens que vous utiliseriez pour partager un mutex.

Malheureusement, les futex peuvent être très difficiles à implémenter (PDF). (Mise à jour 2018, ils ne sont pas aussi effrayants qu'ils l'étaient en 2009).

Au-delà de cela, c'est à peu près la même chose sur les deux plates-formes. Vous effectuez des mises à jour atomiques, pilotées par jeton, d'une structure partagée d'une manière qui (espérons-le) ne provoque pas de famine. Ce qui reste est simplement la méthode pour y parvenir.


6

Sous Windows, une section critique est locale à votre processus. Un mutex peut être partagé / accessible entre les processus. Fondamentalement, les sections critiques sont beaucoup moins chères. Je ne peux pas faire de commentaires sur Linux en particulier, mais sur certains systèmes, ce ne sont que des alias pour la même chose.


6

Juste pour ajouter mes 2 cents, les sections critiques sont définies comme une structure et les opérations sur elles sont effectuées dans un contexte en mode utilisateur.

ntdll! _RTL_CRITICAL_SECTION
   + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG
   + 0x004 LockCount: Int4B
   + 0x008 RecursionCount: Int4B
   + 0x00c OwningThread: Ptr32 Void
   + 0x010 LockSemaphore: Ptr32 Void
   + 0x014 SpinCount: Uint4B

Tandis que les mutex sont des objets noyau (ExMutantObjectType) créés dans le répertoire d'objets Windows. Les opérations Mutex sont principalement implémentées en mode noyau. Par exemple, lors de la création d'un Mutex, vous finissez par appeler nt! NtCreateMutant dans le noyau.


Que se passe-t-il lorsqu'un programme qui initialise et utilise un objet Mutex se bloque? L'objet Mutex est-il automatiquement désalloué? Non, je dirais. Droite?
Ankur

6
Les objets noyau ont un nombre de références. La fermeture d'un handle sur un objet décrémente le nombre de références et lorsqu'il atteint 0, l'objet est libéré. Lorsqu'un processus plante, toutes ses poignées sont automatiquement fermées, de sorte qu'un mutex auquel seul ce processus possède un handle serait automatiquement désalloué.
Michael

Et c'est la raison pour laquelle les objets de la section critique sont liés au processus, d'un autre côté, les mutex peuvent être partagés entre les processus.
Sisir

2

Excellente réponse de Michael. J'ai ajouté un troisième test pour la classe mutex introduite dans C ++ 11. Le résultat est quelque peu intéressant et prend toujours en charge son approbation initiale des objets CRITICAL_SECTION pour des processus uniques.

mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);

LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;

// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    EnterCriticalSection(&critSec);
    LeaveCriticalSection(&critSec);
}

QueryPerformanceCounter(&end);

int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    WaitForSingleObject(mutex, INFINITE);
    ReleaseMutex(mutex);
}

QueryPerformanceCounter(&end);

int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);

// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();

QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
    m.lock();
    m.unlock();
}

QueryPerformanceCounter(&end);

int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);


printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);

Mes résultats étaient 217, 473 et 19 (notez que mon ratio de temps pour les deux derniers est à peu près comparable à celui de Michael, mais ma machine a au moins quatre ans de moins que la sienne, vous pouvez donc voir des preuves d'une vitesse accrue entre 2009 et 2013. , lorsque le XPS-8700 est sorti). La nouvelle classe mutex est deux fois plus rapide que le mutex Windows, mais toujours moins d'un dixième de la vitesse de l'objet Windows CRITICAL_SECTION. Notez que je n'ai testé que le mutex non récursif. Les objets CRITICAL_SECTION sont récursifs (un thread peut les saisir à plusieurs reprises, à condition de laisser le même nombre de fois).


0

Les fonctions AC sont appelées réentrantes si elles n'utilisent que leurs paramètres réels.

Les fonctions réentrantes peuvent être appelées par plusieurs threads en même temps.

Exemple de fonction réentrante:

int reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   return c;
}

Exemple de fonction non réentrante:

int result;

void non_reentrant_function (int a, int b)
{
   int c;

   c = a + b;

   result = c;

}

La bibliothèque standard C strtok () n'est pas réentrante et ne peut pas être utilisée par 2 threads ou plus en même temps.

Certains SDK de plate-forme sont livrés avec la version réentrante de strtok () appelée strtok_r ();

Enrico Migliore

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.