Threadsafe vs re-entrant

Recentemente, ho fatto una domanda, con il titolo “È malloc thread safe?” e dentro di esso ho chiesto “È malloc re-entrant?”

Avevo l’impressione che tutti i rientranti fossero sicuri per i thread.

Questa supposizione è sbagliata?

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

Alcune funzioni richiedono un posto dove archiviare un “work in progress”, le funzioni re-entrant ti permettono di specificare questo puntatore all’interno della memoria del thread stesso, non in un globale. Poiché questo spazio di archiviazione è esclusivo della funzione chiamante, può essere interrotto e reinserito (rientranti) e poiché nella maggior parte dei casi l’esclusione reciproca oltre a ciò che la funzione implementa non è necessaria perché funzioni, vengono spesso considerati filo sicuro . Questo non è, tuttavia, garantito per definizione.

errno, tuttavia, è un caso leggermente diverso sui sistemi POSIX (e tende a essere lo strano in ogni spiegazione di come tutto funzioni) 🙂

In breve, il rientro spesso significa thread sicuro (come in “usa la versione rientrante di quella funzione se stai usando i thread”), ma thread-safe non significa sempre re-entrant (o il contrario). Quando guardi alla sicurezza del thread, la concorrenza è ciò a cui devi pensare. Se si deve fornire un metodo di blocco e di esclusione reciproca per utilizzare una funzione, la funzione non è intrinsecamente thread-safe.

Ma non tutte le funzioni devono essere esaminate per entrambi. malloc() non ha bisogno di essere rientranti, non dipende da nulla fuori dal campo di applicazione del punto di ingresso per un dato thread (ed è esso stesso thread-safe).

Le funzioni che restituiscono i valori allocati staticamente non sono thread-safe senza l’uso di un meccanismo mutex, futex o altro meccanismo di blocco atomico. Tuttavia, non hanno bisogno di essere rientranti se non verranno interrotti.

vale a dire:

 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, l’utilizzo di più thread senza un qualche tipo di blocco sarebbe un disastro .. ma non ha alcuno scopo essere ri-entrante. Vi imbatterete in questo quando la memoria allocata dynamicmente è tabù su alcune piattaforms embedded.

Nella programmazione puramente funzionale, il rientro spesso non implica thread-safe, ma dipende dal comportamento di funzioni definite o anonime passate al punto di ingresso della funzione, alla ricorsione, ecc.

Un modo migliore per mettere “thread safe” è sicuro per l’accesso simultaneo , che meglio illustra la necessità.

Dipende dalla definizione. Ad esempio Qt usa il seguente:

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

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

Quindi, una funzione thread-safe è sempre rientranti, ma una funzione di rientro non è sempre thread-safe.

Per estensione, una class è detta rientrante se le sue funzioni membro possono essere chiamate in sicurezza da più thread, purché ogni thread usi una diversa istanza della class. La class è 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 class.

ma avvertono anche:

Nota: la terminologia nel dominio del multithreading non è interamente standardizzata. POSIX utilizza definizioni di rientranti e thread-safe che sono in qualche modo differenti per le sue API C. Quando si utilizzano altre librerie di classi C ++ orientate agli oggetti con Qt, assicurarsi che le definizioni siano comprese.

TL; DR: Una funzione può essere rientranti, thread-safe, entrambi o nessuno dei due.

Vale la pena leggere gli articoli di Wikipedia per sicurezza del filo e reentrancy . Ecco alcune citazioni:

Una funzione è thread-safe se:

manipola solo strutture di dati condivise in modo da garantire l’esecuzione sicura di più thread contemporaneamente.

Una funzione è rientrante se:

può essere interrotto in qualsiasi momento durante la sua esecuzione e quindi richiamato in modo sicuro (“re-immesso”) prima che le sue precedenti invocazioni completino l’esecuzione.

Come esempi di possibili ritorni, la Wikipedia fornisce l’esempio di una funzione progettata per essere chiamata dagli interrupt di sistema: supponiamo che sia già in esecuzione quando si verifica un altro interrupt. Ma non pensare di essere al sicuro solo perché non si codifica con gli interrupt di sistema: si possono avere problemi di re-ennesimo in un programma a thread singolo se si utilizzano callback o funzioni ricorsive.

La chiave per evitare confusione è che il reentrant si riferisce a un solo thread in esecuzione. È un concetto dal momento in cui non esistevano sistemi operativi multitasking.

Esempi

(Leggermente modificato dagli articoli di Wikipedia)

Esempio 1: non thread-safe, non rientranti

 /* 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 rientranti

 /* 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, rientranti

 /* 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, reentrant

 /* 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; }