web-dev-qa-db-fra.com

Qu'est-ce que le verrou et le concept rentrants en général?

Je suis toujours confus. Quelqu'un pourrait-il expliquer ce que Reentrant signifie dans différents contextes? Et pourquoi voudriez-vous utiliser réentrant vs non réentrant?

Dites les primitives de verrouillage pthread (posix), sont-elles rentrantes ou non? Quels pièges éviter lors de leur utilisation?

Mutex rentre-t-il?

81
vehomzzz

Verrouillage rentrant

Un verrou réentrant est celui où un processus peut réclamer le verrou plusieurs fois sans se bloquer sur lui-même. Il est utile dans les situations où il n'est pas facile de savoir si vous avez déjà saisi un verrou. Si un verrou n'est pas rentrant, vous pouvez saisir le verrou, puis le bloquer lorsque vous le récupérez, ce qui bloque efficacement votre propre processus.

La réentrance est en général une propriété du code où il n'a pas d'état mutable central qui pourrait être corrompu si le code était appelé pendant son exécution. Un tel appel pourrait être effectué par un autre thread, ou il pourrait être effectué récursivement par un chemin d'exécution provenant de l'intérieur du code lui-même.

Si le code s'appuie sur un état partagé qui pourrait être mis à jour au milieu de son exécution, il n'est pas rentrant, du moins pas si cette mise à jour pourrait le casser.

n cas d'utilisation pour le verrouillage rentrant

Un exemple (quelque peu générique et artificiel) d'une application pour un verrou rentrant pourrait être:

  • Vous avez un calcul impliquant un algorithme qui traverse un graphique (peut-être avec des cycles). Une traversée peut visiter le même nœud plus d'une fois en raison des cycles ou en raison de plusieurs chemins vers le même nœud.

  • La structure des données est soumise à un accès simultané et pourrait être mise à jour pour une raison quelconque, peut-être par un autre thread. Vous devez pouvoir verrouiller des nœuds individuels pour faire face à une corruption potentielle des données en raison des conditions de concurrence. Pour une raison quelconque (peut-être les performances), vous ne voulez pas verrouiller globalement la structure de données entière.

  • Votre calcul ne peut pas conserver des informations complètes sur les nœuds que vous avez visités, ou vous utilisez une structure de données qui ne permet pas de répondre rapidement aux questions "ai-je été ici avant".

    Un exemple de cette situation serait une implémentation simple de l'algorithme de Dijkstra avec une file d'attente prioritaire implémentée en tant que segment binaire ou une recherche en largeur utilisant une liste chaînée simple comme file d'attente. Dans ces cas, l'analyse de la file d'attente pour les insertions existantes est O(N) et vous ne voudrez peut-être pas le faire à chaque itération.

Dans cette situation, le suivi des verrous que vous avez déjà acquis coûte cher. En supposant que vous souhaitiez effectuer le verrouillage au niveau du nœud, un mécanisme de verrouillage rentrant atténue la nécessité de dire si vous avez déjà visité un nœud. Vous pouvez simplement verrouiller aveuglément le nœud, peut-être le déverrouiller après l'avoir sorti de la file d'attente.

Mutex rentrants

Un simple mutex n'est pas rentrant car un seul thread peut être dans la section critique à un moment donné. Si vous saisissez le mutex, puis essayez de le saisir à nouveau, un simple mutex n'a pas suffisamment d'informations pour dire qui le détenait auparavant. Pour ce faire, vous avez besoin d'un mécanisme où chaque thread a un jeton afin que vous puissiez savoir qui a saisi le mutex. Cela rend le mécanisme mutex un peu plus cher, donc vous ne voudrez peut-être pas le faire dans toutes les situations.

L'IIRC l'API POSIX threads offre la possibilité de mutex rentrants et non rentrants.

140

Un verrou rentrant vous permet d'écrire une méthode M qui place un verrou sur la ressource A puis d'appeler M récursivement ou à partir d'un code qui détient déjà un verrou sur A.

Avec un verrou non rentrant, vous auriez besoin de 2 versions de M, une qui se verrouille et une qui ne le fait pas, et une logique supplémentaire pour appeler la bonne.

19
Henk Holterman

Le verrouillage réentrant est très bien décrit dans ce tutoriel .

L'exemple du didacticiel est beaucoup moins artificiel que dans la réponse sur la traversée d'un graphique. Un verrou réentrant est utile dans des cas très simples.

13
Ratna Beresford

Le quoi et pourquoi de mutex récursif ne devrait pas être une chose aussi compliquée décrite dans la réponse acceptée.

Je voudrais écrire ma compréhension après avoir creusé autour du filet.


Tout d'abord, vous devez comprendre que lorsque vous parlez de mutex, les concepts multi-threads sont également impliqués. (mutex est utilisé pour la synchronisation. Je n'ai pas besoin de mutex si je n'ai qu'un seul thread dans mon programme)


Deuxièmement, vous devez connaître la différence entre un mutex normal et un mutex récursif .

Cité de [~ # ~] apue [~ # ~]:

(Un mutex récursif est a) Un type de mutex qui permet au même thread de le verrouiller plusieurs fois sans le déverrouiller au préalable.

La principale différence est que dans le même thread , reverrouiller un verrou récursif ne conduit pas à un blocage, ni bloquer le thread.

Est-ce à dire que le verrouillage récursif ne provoque jamais un blocage
Non, il peut toujours provoquer un blocage comme mutex normal si vous l'avez verrouillé dans un thread sans le déverrouiller et essayez de le verrouiller dans d'autres threads.

Voyons un peu de code comme preuve.

  1. mutex normal avec impasse
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t lock;


void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

sortie:

thread1
thread1 hey hey
thread2

exemple de blocage commun, pas de problème.

  1. mutex récursif avec blocage

Décommentez simplement cette ligne
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
et commentez l'autre.

sortie:

thread1
thread1 hey hey
thread2

Oui, un mutex récursif peut également provoquer un blocage.

  1. mutex normal, reverrouiller dans le même fil
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_mutex_t lock;


void func3(){
    printf("func3\n");
    pthread_mutex_lock(&lock);
    printf("func3 hey hey\n");
}

void * func1(void *arg){
    printf("thread1\n");
    pthread_mutex_lock(&lock);
    func3();
    printf("thread1 hey hey\n");

}


void * func2(void *arg){
    printf("thread2\n");
    pthread_mutex_lock(&lock);
    printf("thread2 hey hey\n");
}

int main(){
    pthread_mutexattr_t lock_attr;
    int error;
//    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
    error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_DEFAULT);
    if(error){
        perror(NULL);
    }

    pthread_mutex_init(&lock, &lock_attr);

    pthread_t t1, t2;

    pthread_create(&t1, NULL, func1, NULL);
    sleep(2); 
    pthread_create(&t2, NULL, func2, NULL);

    pthread_join(t2, NULL);

}

sortie:

thread1
func3
thread2

Blocage dans thread t1, Dans func3.
(J'utilise sleep(2) pour mieux voir que le blocage est d'abord provoqué par le re-verrouillage dans func3)

  1. mutex récursif, reverrouiller dans le même thread

Encore une fois, décommentez la ligne mutex récursive et commentez l'autre ligne.

sortie:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

Blocage dans thread t2, Dans func2. Voir? func3 Se termine et se termine, le reverrouillage ne bloque pas le thread ni ne provoque un blocage.


Alors, dernière question, pourquoi en avons-nous besoin?

Pour la fonction récursive (appelée dans les programmes multi-threads et vous souhaitez protéger certaines ressources/données).

Par exemple. Vous avez un programme multi-thread et appelez une fonction récursive dans le thread A. Vous avez des données que vous souhaitez protéger dans cette fonction récursive, vous utilisez donc le mécanisme mutex. L'exécution de cette fonction est séquentielle dans le thread A, vous devez donc définitivement reverrouiller le mutex en récursivité. Utiliser un mutex normal provoque des blocages. Et mutex récursif est inventé pour résoudre ce problème.

Voir un exemple de la réponse acceptée Quand utiliser un mutex récursif? .

Wikipédia explique très bien le mutex récursif. Vaut vraiment la peine d'être lu. Wikipedia: Reentrant_mutex

1
Rick