Qual è il concetto e il blocco Rientrante in generale?


91

Mi confondo sempre. Qualcuno potrebbe spiegare cosa significa Reentrant in diversi contesti? E perché dovresti usare rientrante o non rientrante?

Diciamo primitive di bloccaggio pthread (posix), sono rientranti o no? Quali insidie ​​dovrebbero essere evitate quando le si utilizza?

Il mutex rientra?

Risposte:


157

Blocco rientro

Un blocco rientrante è quello in cui un processo può richiedere il blocco più volte senza bloccarsi su se stesso. È utile in situazioni in cui non è facile controllare se hai già afferrato un lucchetto. Se un lucchetto non rientra, potresti afferrare il lucchetto, quindi bloccare quando vai a prenderlo di nuovo, bloccando efficacemente il tuo stesso processo.

Il rientro in generale è una proprietà del codice in cui non ha uno stato mutabile centrale che potrebbe essere danneggiato se il codice venisse chiamato durante l'esecuzione. Tale chiamata potrebbe essere effettuata da un altro thread, oppure potrebbe essere eseguita in modo ricorsivo da un percorso di esecuzione proveniente dall'interno del codice stesso.

Se il codice si basa su uno stato condiviso che potrebbe essere aggiornato nel mezzo della sua esecuzione, non è rientrante, almeno non se l'aggiornamento potrebbe interromperlo.

Un caso d'uso per il blocco dei rientri

Un esempio (un po 'generico e artificioso) di una domanda per un blocco di rientro potrebbe essere:

  • Hai qualche calcolo che coinvolge un algoritmo che attraversa un grafico (forse con cicli al suo interno). Un attraversamento può visitare lo stesso nodo più di una volta a causa dei cicli o a causa di più percorsi allo stesso nodo.

  • La struttura dei dati è soggetta ad accesso simultaneo e potrebbe essere aggiornata per qualche motivo, magari da un altro thread. È necessario essere in grado di bloccare i singoli nodi per gestire il potenziale danneggiamento dei dati a causa delle condizioni di competizione. Per qualche motivo (forse per le prestazioni) non vuoi bloccare globalmente l'intera struttura dei dati.

  • Il tuo calcolo non può conservare informazioni complete su quali nodi hai visitato, oppure stai utilizzando una struttura dati che non consente di rispondere rapidamente alle domande "sono già stato qui".

    Un esempio di questa situazione sarebbe una semplice implementazione dell'algoritmo di Dijkstra con una coda prioritaria implementata come un mucchio binario o una ricerca in ampiezza utilizzando un semplice elenco collegato come coda. In questi casi, la scansione della coda per gli inserimenti esistenti è O (N) e potresti non volerla fare a ogni iterazione.

In questa situazione, tenere traccia di quali serrature hai già acquisito è costoso. Supponendo che tu voglia eseguire il blocco a livello di nodo, un meccanismo di blocco rientrante allevia la necessità di dire se hai già visitato un nodo. Puoi semplicemente bloccare alla cieca il nodo, magari sbloccandolo dopo averlo rimosso dalla coda.

Mutex rientranti

Un semplice mutex non rientra in quanto solo un thread può essere nella sezione critica in un dato momento. Se prendi il mutex e poi provi a prenderlo di nuovo, un semplice mutex non ha abbastanza informazioni per dire chi lo stava tenendo in precedenza. Per farlo in modo ricorsivo è necessario un meccanismo in cui ogni thread avesse un token in modo da poter dire chi aveva afferrato il mutex. Questo rende il meccanismo mutex un po 'più costoso, quindi potresti non volerlo fare in tutte le situazioni.

IIRC l'API dei thread POSIX offre l'opzione di mutex rientranti e non rientranti.


2
Sebbene tali situazioni di solito dovrebbero essere evitate comunque, poiché rende difficile evitare anche deadlock, ecc. Il threading è abbastanza difficile comunque senza dubbi sul fatto che tu abbia già un lucchetto.
Jon Skeet

+1, considera anche il caso in cui il lucchetto NON è rientrante, puoi bloccarti se non stai attento. Inoltre, in C, non hai gli stessi meccanismi di altri linguaggi per garantire che il blocco venga rilasciato tante volte quante ne vengono acquisite. Questo può portare a grossi problemi.
user7116

1
questo è esattamente quello che mi è successo ieri: non ho preso in considerazione la questione del rientro e ho finito per eseguire il debug di una situazione di stallo per 5 ore ...
vehomzzz

@ Jon Skeet - Penso che ci siano probabilmente situazioni (vedi il mio esempio un po 'artificioso sopra) in cui tenere traccia delle serrature è impraticabile a causa delle prestazioni o di altre considerazioni.
ConcernedOfTunbridgeWells

21

Un blocco di rientro consente di scrivere un metodo Mche mette un blocco sulla risorsa Ae quindi chiama in Mmodo ricorsivo o dal codice che contiene già un blocco A.

Con un blocco non rientrante, avresti bisogno di 2 versioni di M, una che si blocca e una che non lo fa, e una logica aggiuntiva per chiamare quella giusta.


Questo significa che se ho chiamate ricorsive che acquisiscono lo stesso oggetto di blocco più di una volta - diciamo xvolte da un dato thread, non posso intercalare l'esecuzione senza rilasciare tutti i blocchi acquisiti ricorsivamente (stesso blocco ma per il xnumero di volte)? Se è vero, essenzialmente rende questa implementazione sequenziale. Mi sto perdendo qualcosa?
DevdattaK

Non dovrebbe essere un vero problema mondiale. Si tratta più di un blocco granulare e che un thread non si blocca da solo.
Henk Holterman

16

Il blocco rientrante è descritto molto bene in questo tutorial .

L'esempio nel tutorial è molto meno artificioso rispetto alla risposta sull'attraversamento di un grafico. Una serratura rientrante è utile in casi molto semplici.


3

Il cosa e il perché del mutex ricorsivo non dovrebbe essere una cosa così complicata descritta nella risposta accettata.

Vorrei scrivere la mia comprensione dopo aver scavato un po 'intorno alla rete.


Innanzitutto, dovresti capire che quando si parla di mutex , anche i concetti multi thread sono sicuramente coinvolti. (mutex viene utilizzato per la sincronizzazione. Non ho bisogno di mutex se ho solo 1 thread nel mio programma)


In secondo luogo, dovresti conoscere la differenza tra un mutex normale e un mutex ricorsivo .

Citato da APUE :

(Un mutex ricorsivo è a) Un tipo di mutex che consente allo stesso thread di bloccarlo più volte senza prima sbloccarlo.

La differenza fondamentale è che all'interno dello stesso thread , il blocco di un blocco ricorsivo non porta a un deadlock, né blocca il thread.

Ciò significa che il blocco recusivo non causa mai un deadlock?
No, può ancora causare deadlock come un normale mutex se lo hai bloccato in un thread senza sbloccarlo e provi a bloccarlo in altri thread.

Vediamo del codice come prova.

  1. mutex normale con deadlock
#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);

}

produzione:

thread1
thread1 hey hey
thread2

esempio comune di deadlock, nessun problema.

  1. mutex ricorsivo con deadlock

Basta rimuovere il commento da questa riga
error = pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE);
e commentare l'altra.

produzione:

thread1
thread1 hey hey
thread2

Sì, anche il mutex ricorsivo può causare un deadlock.

  1. mutex normale, ribloccare nello stesso thread
#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);

}

produzione:

thread1
func3
thread2

Deadlock dentro thread t1, dentro func3.
(Io uso sleep(2)per rendere più facile vedere che il deadlock è causato in primo luogo dal ribloccaggio func3)

  1. mutex ricorsivo, blocca nuovamente nello stesso thread

Di nuovo, rimuovi il commento dalla riga mutex ricorsiva e commenta l'altra riga.

produzione:

thread1
func3
func3 hey hey
thread1 hey hey
thread2

Deadlock dentro thread t2, dentro func2. Vedere? func3finisce ed esce, il ribloccaggio non blocca il thread o porta a un deadlock.


Quindi, ultima domanda, perché ne abbiamo bisogno?

Per la funzione ricorsiva (chiamata in programmi multi-thread e si desidera proteggere alcune risorse / dati).

Ad esempio, hai un programma multi thread e chiami una funzione ricorsiva nel thread A. Hai alcuni dati che vuoi proteggere in quella funzione ricorsiva, quindi usi il meccanismo mutex. L'esecuzione di quella funzione è sequenziale nel thread A, quindi bloccheresti sicuramente il mutex in ricorsione. Usa mutex normale causa deadlock. E il mutex risorsivo è stato inventato per risolvere questo problema.

Vedi un esempio dalla risposta accettata Quando usare il mutex ricorsivo? .

La Wikipedia spiega molto bene il mutex ricorsivo. Sicuramente vale la pena leggere. Wikipedia: Reentrant_mutex

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.