Il problema è che fork() copia solo il thread chiamante e tutti i mutex contenuti nei thread figlio saranno bloccati per sempre nel figlio fork. La soluzione pthread era pthread_atfork()
gestori. L'idea era che puoi registrare 3 gestori:un prefork, un gestore genitore e un gestore figlio. Quando fork()
accade prefork viene chiamato prima del fork e dovrebbe ottenere tutti i mutex dell'applicazione. Sia il genitore che il figlio devono rilasciare rispettivamente tutti i mutex nei processi padre e figlio.
Questa non è la fine della storia però! Le biblioteche chiamano pthread_atfork
per registrare gestori per mutex specifici della libreria, ad esempio Libc fa questo. Questa è una buona cosa:l'applicazione non può assolutamente conoscere i mutex detenuti da librerie di terze parti, quindi ogni libreria deve chiamare pthread_atfork
per garantire che i propri mutex vengano ripuliti in caso di fork()
.
Il problema è che l'ordine che pthread_atfork
gestori vengono chiamati per librerie non correlate non è definito (dipende dall'ordine in cui le librerie vengono caricate dal programma). Quindi questo significa che tecnicamente può verificarsi un deadlock all'interno di un gestore di prefork a causa di una race condition.
Ad esempio, considera questa sequenza:
- Il thread T1 chiama
fork()
- I gestori di prefork libc sono chiamati in T1 (ad es. T1 ora contiene tutti i lock libc)
- Successivamente, nel thread T2, una libreria di terze parti A acquisisce il proprio mutex AM, quindi effettua una chiamata libc che richiede un mutex. Questo si blocca, perché i mutex libc sono mantenuti da T1.
- Il thread T1 esegue il gestore prefork per la libreria A, che blocca in attesa di ottenere AM, che è detenuto da T2.
C'è il tuo deadlock e non è correlato ai tuoi mutex o codice.
Questo in realtà è successo su un progetto su cui ho lavorato una volta. Il consiglio che avevo trovato in quel momento era di scegliere la forcella o i fili ma non entrambi. Ma per alcune applicazioni probabilmente non è pratico.
È sicuro eseguire il fork in un programma multithread fintanto che sei molto attento al codice tra fork ed exec. È possibile effettuare solo chiamate di sistema rientranti (ovvero asincrone sicure) in tale intervallo. In teoria, non sei autorizzato a malloc o free lì, anche se in pratica l'allocatore Linux predefinito è sicuro e le librerie Linux hanno fatto affidamento su di esso Il risultato finale è che devi usa l'allocatore predefinito.