2012-02-02 14 views
15

Ero abituato a pensare che tutte le funzioni di rientro siano thread-safe. Ma ho letto Reentrancy page in Wiki, è messaggi Il codice che è "perfettamente rientrante, ma non thread-safe. Perché non garantisce il dato globale è in uno stato coerente durante l'esecuzione"Perché questo codice rientra, ma non è thread-safe

int t; 

void swap(int *x, int *y) 
{ 
     int s; 

     s = t; // save global variable 
     t = *x; 
     *x = *y; 
     // hardware interrupt might invoke isr() here! 
     *y = t; 
     t = s; // restore global variable 
} 

void isr() 
{ 
     int x = 1, y = 2; 
     swap(&x, &y); 
} 

non capisco la sua spiegazione . Perché questa funzione non è thread-safe? È perché la variabile globale int t verrà modificata durante l'esecuzione dei thread?

+0

Questo esempio è più che forzato. Ma, re-enterant e thread-safe sono concetti ortogonali. –

+1

Posix ha un'altra definizione per la rientranza "In POSIX.1c, una" funzione di rientro "è definita come una" funzione il cui effetto, quando viene chiamato da due o più thread, è garantito come se i thread eseguissero la funzione una dopo l'altra in un ordine indefinito, anche se l'esecuzione effettiva è interlacciata ", che quell'esempio (abbastanza brutto) su wikipedia non sarebbe conforme allo – nos

+0

Mi sembra che questo esempio non sia rientranti: l'interrotto' swap() 'sarebbe non scambiare i valori puntati da 'x' e' y' come previsto ('* y' potrebbe essere impostato su 2 qualunque sia il valore iniziale di' * x'). – rom1v

risposta

4

Per dare una risposta più generica, il rientro è solo a livello di funzione. Significa che una chiamata della funzione non cambia uno stato in cui può alterare il funzionamento di una seconda chiamata.

Nell'esempio fornito, la variabile globale non viene modificata tra due chiamate della funzione. Ciò che accade all'interno della funzione non ha influenza su ogni chiamata della funzione.

Un esempio di una funzione non rientrante è strtok

È ad esempio non è possibile nido 2 parsing portamateriali con esso:

/* To read a several lines of comma separated numbers */ 
char buff[WHATEVER], *p1, *p2; 

    p1 = strtok(buff, "\n"); 
    while(p1) { 
    p2 = strtok(p1, ","); 
    while(p2) { 
     atoi(p2); 
     p2 = strtok(NULL, ","); 
     } 
    } 
    p1 = strtok(NULL, "\n"); 
    } 

Questo non funziona, perché lo stato della esterno il loop strtok è bloccato dalla seconda chiamata (è necessario utilizzare la variante rientrante strtok_r).

+3

La domanda chiedeva una spiegazione di come le funzioni di rientro possono essere non è thread-safe Questo non risponde alla domanda, fornisce semplicemente un esempio del contrario. – Jed

0

Così la funzione mette in disordine una variabile globale chiamata t per qualche strana ragione. Se questa funzione viene chiamata contemporaneamente da due thread diversi, è possibile che si ottengano risultati imprevisti e non corretti perché un'istanza sovrascriverà il valore in t che è stato scritto dall'altra istanza.

1

Se si eseguono 2 istanze (ognuna su un thread diverso) eseguendolo, è possibile intervenire sulle dita degli altri: se si è interrotto al commento "interrupt hardware" e un altro eseguito, potrebbe cambiare t, in modo che tornare al primo avrebbe prodotto risultati errati.

4

Il trucco con questo tipo di rientranza è che l'esecuzione della prima chiamata si interrompe mentre viene eseguita la seconda chiamata. Proprio come una chiamata alla sottofunzione. La prima chiamata continua dopo la seconda chiamata completamente terminata. Poiché la funzione salva lo stato di t alla voce e la ripristina all'uscita, non è cambiato nulla per la prima chiamata quando continua. Pertanto, hai sempre un ordine di esecuzione definito e rigoroso, indipendentemente da dove viene interrotta esattamente la prima chiamata.

Quando questa funzione viene eseguita in più thread, tutte le esecuzioni vengono eseguite in parallelo, anche in vero parallelo con una CPU multicore. Non esiste un ordine definito di esecuzione su tutti i thread, solo all'interno di un singolo thread. Quindi il valore di t può essere modificato in qualsiasi momento da uno degli altri thread.

+0

Quindi la rientranza non include il parallel? ad esempio, foo() {a(); b()}, quando avviene la rientranza , potrebbe essere solo un (ab) b, ma puo ' essere un a b b quale multithread potrebbe? –

+0

Fondamentalmente si. La rientranza riportata qui è sempre (a2 b2), perché la chiamata interna viene sempre eseguita completamente prima che la chiamata esterna continui. Nel multithreading, dipende dalla pianificazione del thread e potrebbe essere anche a1 a2 b1 b2. Rendere funzionale una funzione non significa che sia thread-safe. E l'altro modo: renderlo thread-safe non significa che sia rientranti. Entrambi devono essere gestiti separatamente, è sempre la domanda se e come lo stato (cioè le variabili) è condiviso tra le chiamate e tra i thread. – Secure

+0

Solo per nota. Per me, è stato davvero difficile immaginare * "ri-entrare solo dallo stesso thread" * situazione. Poiché temo heisenbug, mi sono allenato a implicare l'esecuzione parallela su argomenti di discussione. E ho visto il termine * rientranza * solo dal thread relativo al testo. Penso che questo sia lo stesso per le persone che hanno iniziato a programmare dopo che la multi-threading preventiva è diventata comune. Ma ironia della sorte, questo è il punto chiave per comprendere il termine. – Eonil

2

Sto tentando di offrire un altro esempio (forse meno forzato) di una funzione rientrante, ma non thread-safe.

Ecco un'implementazione della "torri di Hanoi", utilizzando una "temp" globale condivisa impilare:

stack_t tmp; 

void hanoi_inner(stack_t src, stack_t dest, stack_t tmp, int n) 
{ 
    if (n == 1) move(src, dest) 
    else { 
    hanoi_inner(src, tmp, dest, n - 1); 
    move(src, dest); 
    hanoi_inner(tmp, dest, src, n - 1); 
    } 
} 

void hanoi(stack_t src, stack_t dest, int n) { hanoi_inner(src, dest, tmp, n); } 

La funzione hanoi() è rientrante perché lascia lo stato del buffer globale tmp invariata quando restituisce (un avvertimento: il solito vincolo di avere una dimensione crescente di dischi su tmp può essere violato durante una chiamata di rientro.) Tuttavia hanoi() non è thread-safe.

Ecco un esempio che è sia thread-safe e rientrante se l'operatore di incremento n++ è atomico:

int buf[MAX_SIZE]; /* global, shared buffer structure */ 
int n;    /* global, shared counter */ 

int* alloc_int() { return &buf[n++]; } 

È davvero potrebbe utilizzare questo come un allocatore per celle da un numero intero (non verifica overflow, lo so). Se n++ non è un'operazione atomica, due thread o due chiamate rientranti potrebbero facilmente finire per essere allocata alla stessa cella.

+1

Questo è solo rientrabile se n ++ è atomico (che di solito non lo è). Altrimenti entrambe le chiamate potrebbero finire per restituire lo stesso puntatore. –

+0

@per Grazie - Proverò a cambiarlo per essere rientranti senza ricorrere al locking o ad un atomico n ++ – gcbenison

3

assumere filo A e filo B. thread A ha due variabili locali a = 5, b = 10 e filettatura B ha due variabili locali p = 20, q = 30.

thread A chiama: swap (& a, & b);

Chiamate con thread B: scambio (& p, & q);

Suppongo che entrambi i thread siano in esecuzione su diversi core e appartengano allo stesso processo. La variabile t è globale e int x, int y sono locali alla funzione come data. La seguente pianificazione dei thread mostra come il valore di 't' può variare in base alla pianificazione dei thread e quindi rende il thread del codice non sicuro. Dì globale t = 100;

Thread A   Thread B 
1) int s;  int s; 
2) s = 100;  s = 100; 
3) t = 5;  no operation(nop); 
4) nop;   t = 20; // t is global so Thread A also sees the value as t = 20 
5) x = 10;  x = 30; 
6) y = 20;  y = 20; // Thread A exchange is wrong, Thread B exchange is OK 

Ora prova ad immaginare cosa sarebbe successo se le istruzioni 3 e 4 fossero in ordine diverso sopra. t otterrebbe quindi il valore 5 e lo scambio nel thread B sarebbe sbagliato. La situazione è ancora più semplice se i due thread si trovano sullo stesso processore. Quindi nessuna delle operazioni sopra è simultanea. Ho appena mostrato l'interleaving nei passaggi 3 e 4 in quanto questi sono i più importanti.

Problemi correlati