Cosa cerchi durante il debug dei deadlock?


25

Di recente ho lavorato a progetti che utilizzano fortemente il threading. Penso di essere a posto nel progettarli; usa il più possibile la progettazione senza stato, blocca l'accesso a tutte le risorse di cui ha bisogno più di un thread, ecc. La mia esperienza nella programmazione funzionale mi ha aiutato moltissimo.

Tuttavia, quando leggo il codice thread di altre persone, mi confondo. Sto eseguendo il debug di un deadlock in questo momento, e poiché lo stile e il design della codifica sono diversi dal mio stile personale, ho difficoltà a vedere potenziali condizioni di deadlock.

Cosa cerchi durante il debug dei deadlock?


Lo sto chiedendo qui invece di SO perché voglio indicazioni più generali sul debug dei deadlock, non una risposta specifica al mio problema.
Michael K,

Le strategie a cui riesco a pensare sono il logging (come molti altri hanno sottolineato), in realtà esaminando il grafico di deadlock di who's-wait-for-a-lock-hold-by-who (vedi stackoverflow.com/questions/3483094/… per alcuni puntatori) e bloccare le annotazioni (consultare clang.llvm.org/docs/ThreadSafetyAnalysis.html ). Anche se non è il tuo codice, potresti provare a convincere l'autore ad aggiungere annotazioni: probabilmente troveranno bug e li risolveranno (possibilmente includendo il tuo) nel processo.
Don Hatch,

Risposte:


23

Se la situazione è un vero deadlock (ovvero due thread contengono due blocchi diversi, ma almeno un thread desidera un blocco che l'altro thread detiene), è necessario innanzitutto abbandonare tutte le preconcetti di come i thread ordinano il blocco. Non assumere niente. Potresti voler rimuovere tutti i commenti dal codice che stai guardando, poiché questi commenti potrebbero farti credere in qualcosa che non è vero. È difficile enfatizzarlo abbastanza: non supporre nulla.

Successivamente, determina quali blocchi vengono bloccati mentre un thread tenta di bloccare qualcos'altro. Se puoi, assicurati che un thread si sblocchi in ordine inverso rispetto al blocco. Ancora meglio, assicurati che un thread contenga solo un lucchetto alla volta.

Lavorare scrupolosamente attraverso l'esecuzione di un thread ed esaminare tutti gli eventi di blocco. Ad ogni blocco, determinare se un thread contiene altri blocchi e, in tal caso, in quali circostanze un altro thread, eseguendo un percorso di esecuzione simile, può accedere all'evento di blocco in esame.

È certamente possibile che non troverai il problema prima di rimanere senza tempo o denaro.


4
+1 Wow, è pessimista ... non è la verità, però. È un dato di fatto che non puoi trovare tutti i bug. Grazie per i suggerimenti!
Michael K,

Bruce, la tua caratterizzazione di "vero punto morto" mi sorprende. Pensavo che un deadlock tra due thread fosse quando ognuno sta aspettando un lock che l'altro tiene. La tua definizione sembra includere anche il caso in cui un thread, mentre tiene un blocco, attende di acquisire un secondo blocco che è attualmente trattenuto da un thread diverso. Non mi sembra un deadlock; è??
Don Hatch,

@DonHatch - L'ho espresso male. La situazione che descrivi non è deadlock. Avevo sperato di trasmettere il disordine del debug di una situazione che include un blocco di blocco thread A, quindi tentando di acquisire il blocco B, mentre il thread che contiene il blocco B sta cercando di acquisire il blocco A. Forse. O forse la situazione è molto più complicata. Devi solo tenere una mente molto aperta sull'ordine di acquisizione dei blocchi. Esamina tutti i presupposti. Non fidarti di niente.
Bruce Ediger

+1 che suggerisce di leggere attentamente il codice ed esaminare tutte le operazioni di blocco in modo isolato. È molto più facile guardare un grafico complesso esaminando attentamente un singolo nodo piuttosto che cercare di vedere tutto in una volta. Quante volte ho riscontrato il problema semplicemente fissando il codice ed eseguendo diversi scenari nella mia testa.
Newtopian,

11
  1. Come altri hanno già detto ... se puoi ottenere informazioni utili per la registrazione, prova prima perché è la cosa più semplice da fare.

  2. Identificare i blocchi coinvolti. Cambia tutti i mutex / semafori che aspettano per sempre le attese cronometrate ... qualcosa di ridicolmente lungo come 5 minuti. Registra l'errore quando scade. Questo ti indirizzerà almeno nella direzione di uno dei blocchi coinvolti nel problema. A seconda della variabilità dei tempi potresti essere fortunato e trovare entrambi i blocchi dopo poche corse. Utilizzare il codice / condizioni di errore della funzione per registrare una traccia di pseudo stack dopo che l'attesa temporizzata non riesce a identificare come ci si è arrivati ​​in primo luogo. Ciò dovrebbe aiutarti a identificare il thread coinvolto nel problema.

  3. Un'altra cosa che potresti provare è costruire una libreria wrapper attorno ai tuoi servizi mutex / semaphore. Tieni traccia di quali thread hanno ciascun mutex e quali thread sono in attesa sul mutex. Crea un thread di monitoraggio che controlla da quanto tempo i thread hanno bloccato. Attiva una durata ragionevole e scarica le informazioni sullo stato che stai monitorando.

Ad un certo punto, sarà necessaria la semplice ispezione del vecchio codice.


6

Il primo passo (come dice Péter) è la registrazione. Sebbene nella mia esperienza questo sia spesso problematico. Nell'elaborazione parallela pesante questo spesso non è possibile. Una volta ho dovuto eseguire il debug di qualcosa di simile con una rete neurale, che ha elaborato 100k di nodi al secondo. L'errore si è verificato solo dopo diverse ore e anche una sola riga di output ha rallentato così tanto le cose che ci sarebbero voluti giorni. Se la registrazione è possibile, concentrati meno sui dati, ma più sul flusso del programma, fino a quando non sai in quale parte accade. Solo una semplice riga all'inizio di ogni funzione e se riesci a trovare la funzione giusta, suddividila in blocchi più piccoli.

Un'altra opzione è rimuovere parti del codice e dei dati per localizzare il bug. Forse anche scrivere qualche piccolo programma che prende solo alcune delle classi ed esegue solo i test più elementari (ovviamente in più thread ovviamente). Rimuovi tutto ciò che riguarda la GUI, ad esempio qualsiasi output sullo stato di elaborazione effettivo. (Ho trovato che l'interfaccia utente è la fonte del bug abbastanza spesso)

Nel tuo codice prova a seguire il flusso logico completo di controllo tra l'inizializzazione del blocco e il rilascio. Un errore comune potrebbe essere quello di bloccare all'inizio di una funzione, sbloccare alla fine, ma avere una dichiarazione di ritorno condizionale da qualche parte nel mezzo. Eccezioni potrebbero impedire anche il rilascio.


"Le eccezioni potrebbero impedire il rilascio" -> I linguaggi di peccato che non hanno variabili con ambito: /
Matthieu M.

1
@Matthieu: avere variabili con ambito e effettivamente usarle correttamente possono essere due cose diverse. E ha chiesto possibili problemi in generale, senza menzionare una lingua specifica. Quindi questa è una cosa che può influenzare il flusso di controllo.
Thorsten Müller,

3

I miei migliori amici sono stati dichiarazioni di stampa / registro in luoghi interessanti all'interno del codice. Questi di solito mi aiutano a capire meglio cosa sta realmente accadendo all'interno dell'app, senza interrompere i tempi tra i diversi thread, il che potrebbe impedire la riproduzione del bug.

Se fallisce, il mio unico metodo rimanente è fissare il codice e cercare di costruire un modello mentale dei vari thread e interazioni, e cercare di pensare a possibili modi folli per ottenere ciò che apparentemente è successo :-) Ma non mi considero un assassino deadlock molto esperto. Spero che altri saranno in grado di dare idee migliori, dalle quali posso imparare anche io :-)


1
Oggi ho eseguito il debug di un paio di blocchi morti come questo. Il trucco era avvolgere pthread_mutex_lock () con una macro che stampa la funzione, il numero di riga, il nome del file e il nome della variabile mutex (mediante tokenizzazione) prima e dopo l'acquisizione del blocco. Fai lo stesso anche per pthread_mutex_unlock (). Quando ho visto che il mio thread si bloccava, dovevo solo guardare gli ultimi due messaggi, c'erano due thread che cercavano di bloccare ma non lo finivano mai! Ora non resta che aggiungere un meccanismo per attivare questa opzione in fase di esecuzione. :-)
Plumenator

3

Prima di tutto, cerca di ottenere l'autore di quel codice. Probabilmente avrà l'idea di ciò che ha scritto. anche se voi due non riuscite a individuare il problema semplicemente parlando, almeno potete sedervi con lui per individuare la porzione di deadlock, che sarà molto più veloce di quanto capiate il suo codice senza aiuto.

In caso contrario, come ha detto Péter Török, la registrazione è probabilmente la strada giusta. Per quanto ne so, Debugger ha fatto un brutto lavoro nell'ambiente multi-threading. cerca di individuare dove si trova il lucchetto, ottieni un insieme di quali risorse sono in attesa e in quali condizioni si verificano le condizioni di gara.


no, la registrazione è il tuo nemico qui: quando esegui l'accesso lento, cambi il comportamento del programma al punto in cui è facile ottenere un programma che funzioni perfettamente bene con la registrazione abilitata, ma i deadlock si bloccano quando la registrazione è disattivata. È lo stesso tipo di problema che avresti quando esegui un programma su una CPU singola anziché multicore.
gbjbaanb,

@gbjbaanb, penso che dire che il tuo nemico è troppo duro. Forse sarebbe corretto dire che è il tuo migliore amico, che ti delude di tanto in tanto. Concordo con molte altre persone su questa pagina che affermano che la registrazione è un buon primo passo da fare, dopo che l'esame del codice non è riuscito - spesso (in effetti la maggior parte delle volte, nella mia esperienza) individuerà una semplice strategia di registrazione il problema facilmente e il gioco è fatto. Altrimenti ricorrere ad altri metodi, ma non penso che sia un buon consiglio evitare di provare quello che è spesso lo strumento migliore per il lavoro solo perché non è sempre utile.
Don Hatch,

0

Questa domanda mi attira;) Innanzitutto, considerati fortunato dal momento che sei stato in grado di riprodurre il problema in modo coerente su ogni corsa. Se ricevi la stessa eccezione con lo stesso stacktrace ogni volta, dovrebbe essere abbastanza semplice. In caso contrario, non fidarti troppo dello stacktrace, invece controlla semplicemente l'accesso agli oggetti globali e il suo stato cambia durante l'esecuzione.


0

Se devi eseguire il debug dei deadlock, sei già nei guai. Di norma, utilizzare i lucchetti per il minor tempo possibile - o per niente, se possibile. Dovresti evitare qualsiasi situazione in cui prendi un lucchetto e poi passi a un codice non banale.

Dipende ovviamente dal tuo ambiente di programmazione, ma dovresti considerare cose come le code sequenziali che potrebbero permetterti di accedere a una risorsa da un singolo thread.

E poi c'è una strategia vecchia ma infallibile: assegnare un "livello" a ciascun blocco, a partire dal livello 0. Se si prende un blocco di livello 0 non è consentito alcun altro blocco. Dopo aver ottenuto un blocco di livello 1 puoi prendere un blocco di livello 0. Dopo aver ottenuto un blocco di livello 10 puoi prendere blocchi di livello 9 o inferiore ecc.

Se lo trovi impossibile, devi correggere il codice perché ti imbatterai in deadlock.

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.