Threadsafe vs rientro


Risposte:


42

Le funzioni di rientro non si basano su variabili globali che sono esposte nelle intestazioni della libreria C .. prendi strtok () vs strtok_r () per esempio in C.

Alcune funzioni necessitano di una posizione in cui memorizzare un "lavoro in corso", le funzioni rientranti consentono di specificare questo puntatore all'interno della memoria del thread, non in una globale. Poiché questa memorizzazione è esclusiva della funzione chiamante, può essere interrotta e reinserita (rientro) e poiché nella maggior parte dei casi l'esclusione reciproca oltre a ciò che la funzione implementa non è richiesta affinché questa funzioni, sono spesso considerati thread-safe . Tuttavia, questo non è garantito per definizione.

errno, tuttavia, è un caso leggermente diverso sui sistemi POSIX (e tende ad essere il bizzarro in ogni spiegazione di come funziona tutto questo) :)

In breve, rientrante spesso significa thread-safe (come in "usa la versione rientrante di quella funzione se stai usando thread"), ma thread-safe non sempre significa rientrante (o il contrario). Quando guardi alla sicurezza dei thread, la concorrenza è ciò a cui devi pensare. Se è necessario fornire un mezzo di blocco e di mutua esclusione per utilizzare una funzione, la funzione non è intrinsecamente thread-safe.

Ma non tutte le funzioni devono essere esaminate neanche per. malloc()non ha bisogno di essere rientrante, non dipende da nulla al di fuori dell'ambito del punto di ingresso per un dato thread (ed è esso stesso thread-safe).

Le funzioni che restituiscono valori allocati staticamente non sono thread-safe senza l'uso di un mutex, futex o altri meccanismi di blocco atomico. Tuttavia, non hanno bisogno di essere rientrati se non verranno interrotti.

cioè:

static char *foo(unsigned int flags)
{
  static char ret[2] = { 0 };

  if (flags & FOO_BAR)
    ret[0] = 'c';
  else if (flags & BAR_FOO)
    ret[0] = 'd';
  else
    ret[0] = 'e';

  ret[1] = 'A';

  return ret;
}

Quindi, come puoi vedere, avere più thread che lo usano senza un qualche tipo di blocco sarebbe un disastro .. ma non ha alcuno scopo essere rientranti. Ti imbatterai in questo quando la memoria allocata dinamicamente è tabù su alcune piattaforme incorporate.

Nella programmazione puramente funzionale, rientrante spesso non implica thread safe, dipenderebbe dal comportamento di funzioni definite o anonime passate al punto di ingresso della funzione, ricorsione, ecc.

Un modo migliore per mettere "thread safe" è sicuro per l'accesso simultaneo , il che illustra meglio la necessità.


2
Rientrante non implica thread-safe. Le funzioni pure implicano la sicurezza dei thread.
Julio Guerra

Ottima risposta Tim. Giusto per chiarire, la mia comprensione dal tuo "spesso" è che thread-safe non implica rientrante, ma anche rientrante non implica thread-safe. Saresti in grado di trovare un esempio di una funzione rientrante che non è thread-safe?
Riccardo

@ Tim Post "In breve, rientrante spesso significa thread-safe (come in" usa la versione rientrante di quella funzione se stai usando thread "), ma thread safe non sempre significa rientrante." qt dice l' opposto: "Quindi, una funzione thread-safe è sempre rientrante, ma una funzione rientrante non è sempre thread-safe".
4pie0

e wikipedia dice ancora qualcos'altro: "Questa definizione di rientranza differisce da quella di thread-safety in ambienti multi-thread. Una subroutine rientrante può raggiungere la thread-safety, [1] ma essere rientrante da sola potrebbe non essere sufficiente per essere thread-safe in tutte le situazioni. Al contrario, il codice thread-safe non deve necessariamente essere rientrante (...) "
4pie0

@Riccardo: Le funzioni sincronizzate tramite variabili volatili ma non le barriere di memoria piena per l'uso con gestori di segnali / interrupt sono generalmente rientranti ma thread-safe.
doynax

77

TL; DR: una funzione può essere rientrante, thread-safe, entrambe o nessuna delle due.

Vale la pena leggere gli articoli di Wikipedia sulla sicurezza dei thread e sul rientro . Ecco alcune citazioni:

Una funzione è thread-safe se:

manipola solo le strutture di dati condivise in un modo che garantisce un'esecuzione sicura da parte di più thread contemporaneamente.

Una funzione è rientrante se:

può essere interrotto in qualsiasi momento durante la sua esecuzione e quindi richiamato di nuovo in sicurezza ("reinserito") prima che le sue precedenti invocazioni completino l'esecuzione.

Come esempi di possibile rientro, Wikipedia fornisce l'esempio di una funzione progettata per essere chiamata da interruzioni di sistema: supponiamo che sia già in esecuzione quando si verifica un'altra interruzione. Ma non pensare di essere al sicuro solo perché non codifichi con interruzioni di sistema: puoi avere problemi di rientro in un programma a thread singolo se usi callback o funzioni ricorsive.

La chiave per evitare confusione è che rientrante si riferisce a un solo thread in esecuzione. È un concetto del tempo in cui non esistevano sistemi operativi multitasking.

Esempi

(Leggermente modificato dagli articoli di Wikipedia)

Esempio 1: non thread-safe, non rientrante

/* As this function uses a non-const global variable without
   any precaution, it is neither reentrant nor thread-safe. */

int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

Esempio 2: thread-safe, non rientrante

/* We use a thread local variable: the function is now
   thread-safe but still not reentrant (within the
   same thread). */

__thread int t;

void swap(int *x, int *y)
{
    t = *x;
    *x = *y;
    *y = t;
}

Esempio 3: non thread-safe, rientrante

/* We save the global state in a local variable and we restore
   it at the end of the function.  The function is now reentrant
   but it is not thread safe. */

int t;

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

Esempio 4: thread-safe, rientrante

/* We use a local variable: the function is now
   thread-safe and reentrant, we have ascended to
   higher plane of existence.  */

void swap(int *x, int *y)
{
    int t;
    t = *x;
    *x = *y;
    *y = t;
}

10
So che non dovrei commentare solo per dire grazie, ma questa è una delle migliori illustrazioni che illustra le differenze tra le funzioni rientranti e thread safe. In particolare hai usato termini chiari molto concisi e hai scelto una grande funzione di esempio per distinguere tra le 4 categorie. Quindi grazie!
ryyker

11
Mi sembra che l'esempio 3 non sia rientrante: se un gestore di segnali, interrompendo dopo t = *x, chiama swap(), allora tverrà sovrascritto, portando a risultati inaspettati.
rom1v

1
@ SandBag_1996, consideriamo una chiamata per swap(5, 6)essere interrotta da un file swap(1, 2). Dopo t=*x, s=t_originale t=5. Ora, dopo l'interruzione, s=5e t=1. Tuttavia, prima che il secondo swapritorni ripristinerà il contesto, rendendo t=s=5. Ora torniamo al primo swapcon t=5 and s=t_originale continuiamo dopo t=*x. Quindi, la funzione sembra essere rientrata. Ricorda che ogni chiamata riceve la propria copia di sallocato in pila.
urnonav

4
@ SandBag_1996 Il presupposto è che se la funzione viene interrotta (in qualsiasi momento), deve solo essere richiamata di nuovo e aspettiamo fino al suo completamento prima di continuare la chiamata originale. Se succede qualcos'altro, è fondamentalmente multithreading e questa funzione non è thread-safe. Supponiamo che la funzione esegua ABCD, accettiamo solo cose come AB_ABCD_CD, o A_ABCD_BCD, o anche A__AB_ABCD_CD__BCD. Come puoi verificare, l'esempio 3 funzionerebbe bene con questi presupposti, quindi è rientrante. Spero che sia di aiuto.
MiniQuark

1
@ SandBag_1996, mutex lo renderebbe effettivamente non rientrante. La prima chiamata blocca mutex. Arriva la seconda invocazione: deadlock.
urnonav

56

Dipende dalla definizione. Ad esempio Qt utilizza quanto segue:

  • Una funzione thread-safe * può essere chiamata simultaneamente da più thread, anche quando le chiamate utilizzano dati condivisi, poiché tutti i riferimenti ai dati condivisi sono serializzati.

  • Una funzione rientrante può anche essere chiamata simultaneamente da più thread, ma solo se ogni chiamata utilizza i propri dati.

Quindi, una funzione thread-safe è sempre rientrante, ma una funzione rientrante non è sempre thread-safe.

Per estensione, si dice che una classe rientri se le sue funzioni membro possono essere chiamate in sicurezza da più thread, purché ogni thread utilizzi un'istanza diversa della classe. La classe è thread-safe se le sue funzioni membro possono essere chiamate in modo sicuro da più thread, anche se tutti i thread utilizzano la stessa istanza della classe.

ma avvertono anche:

Nota: la terminologia nel dominio multithreading non è completamente standardizzata. POSIX utilizza definizioni di rientrante e thread-safe che sono leggermente diverse per le sue API C. Quando si usano altre librerie di classi C ++ orientate agli oggetti con Qt, assicurarsi che le definizioni siano comprese.


2
Questa definizione di rientrante è troppo forte.
qweruiop

Una funzione è sia rientrante che thread-safe se non utilizza alcuna variabile globale / statica. Thread-safe: quando molti thread eseguono la tua funzione contemporaneamente, c'è qualche gara? Se usi la variabile globale, usa lock per proteggerla. quindi è thread-safe. rientrante: se si verifica un segnale durante l'esecuzione della funzione e richiama nuovamente la funzione nel segnale, è sicuro ?? in tal caso, non ci sono più thread. È meglio che tu non usi alcuna
variabile
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.