Ci sono modi per testare il tuo codice multi-threaded per condizioni di gara e deadlock?
Per vedere se stanno eseguendo il modo in cui dovrebbero essere ...
Ci sono modi per testare il tuo codice multi-threaded per condizioni di gara e deadlock?
Per vedere se stanno eseguendo il modo in cui dovrebbero essere ...
CHESS , un progetto di Microsoft Research. Citando il loro sito:
CHESS is a tool for finding and reproducing Heisenbugs in concurrent programs. CHESS repeatedly runs a concurrent test ensuring that every run takes a different interleaving. If an interleaving results in an error, CHESS can reproduce the interleaving for improved debugging. CHESS is available for both managed and native programs.
Aggiornamento (23/09/2015): per C, C ++ e Go, puoi utilizzare ThreadSanitizer .
Valgrind ha Helgrind che aiuta davvero. Non solo aiuta a indicare le gare che potrebbero portare alla fame o al deadlock, il leggero rallentamento del profilo del programma a volte espone le razze che potrebbero non essere viste altrimenti.
Quindi, anche se vai in commando con un qualche tipo di metodo lock free, aiuta ancora:)
Tuttavia, è POSIX centrico. Viene fornito con intestazioni che rendono facilmente semplici librerie di test di unità come TAP consapevole che è in esecuzione, il che è anche molto utile. Ad esempio, potresti avere un thread che normalmente non bloccherebbe quando provi ad acquisire un blocco, procedi e blocca (forse in modo casuale), solo per simulare la fame.
Non ricordo esattamente i dettagli, ma questa è l'idea generale. E l'ho fatto solo una volta, ma quello che ho fatto è stato separare il codice di rientro dal codice che esegue l'attività, utilizzando un'interfaccia per essere in grado di prendere in giro la classe di attività.
Poi ho progettato il mio mock-up per poter bloccare una chiamata in modo da sapere che il thread si trova nella sezione critica, quindi richiamarlo di nuovo e verificare che sia in attesa, prima di rilasciare il primo thread e terminare in modo pulito .
Qualcosa del genere.
Non sono sicuro che funzioni per scenari più complessi, ma aiuta a preservare il comportamento durante i refactoring.
Al JAOO / GOTO quest'anno ho visto questa presentazione:
Il trucco è quello di modellare ciò che la tua applicazione hairball dovrebbe fare, in termini di passaggi di invocazione così come le operazioni effettive sulla tua applicazione. Il software John Hughes quindi esegue sistematicamente numerose permutazioni dei passaggi di chiamata ripetutamente in parallelo e verifica successivamente che lo stato dell'applicazione corrisponda allo stato del modello. Se viene rilevato un errore, il software sa come ridurre i passaggi al caso minimo che produce l'errore.
Ha dimostrato dal vivo come catturare diversi bug nelle librerie di base di Erlang che erano in agguato da 15 anni e occasionalmente segnalato, ma nessuno riusciva a capire da dove provenissero e quindi a risolvere il problema. Con i casi minimi riportati dal software, il manutentore della libreria è stato in grado di correggere ogni bug entro un giorno .
È stato così impressionante.
John Hughes vende questo software attraverso la sua azienda.
Puoi provare il mio Relacy Race Detector . È progettato per verificare con cura e precisione gli algoritmi di sincronizzazione come code produttore-consumatore e contenitori simultanei, ma non è molto adatto per la verifica di interi programmi. Tuttavia, forse è una buona idea distribuire comunque la sincronizzazione e i mutex su un programma, ma concentrare la sincronizzazione in componenti specializzati (che possono essere verificati con Relacy).
Non è facile, ma fondamentalmente l'unico modo è chiamare il codice multi-thread simultaneamente da più thread e cambiare i tempi e ordinare a caso giocando con% casualeThread.sleep()
e Thread.yield()
chiamate ( assumendo Java).
Ci sono anche strumenti pronti disponibili (come TestNG) che fanno qualcosa come descritto sopra, ma non sono ancora molto maturi, per quanto ne so.
Non un severo test unitario, ma un controllo di runtime che mi ha aiutato con alcuni test intermittenti in errore. È veloce e amp; sporco, ma ha funzionato.
Quando viene concesso un mutex, tengo traccia di quale thread ha. Tutte le richieste mutex hanno un timeout di trenta secondi, dopo di che gridano deadlock.
Posso quindi usare l'elenco dei mutex concessi per vedere quale thread ha il blocco mutex, e perché per così tanto tempo. Nei miei casi finora è perché è stato bloccato su qualcos'altro, quindi posso quindi sistemare quella situazione.
Questo ha funzionato per me perché i miei mutex hanno una classe wrapper multipiattaforma che semplifica l'inserimento della registrazione e del timeout. Sapevo anche abbastanza sull'applicazione per sapere che non avrebbe mai dovuto bloccare un mutex per 30 secondi.
Potrebbe non essere completamente generico, ma consente di risparmiare un sacco di debug per circa un paio d'ore di programmazione. L'overhead è trascurabile e può essere solo di debug-build.
Vedrò di estenderlo per registrare sequenze di richieste mutex annidate, e vedere se vi sono potenzialmente induzioni di deadlock (ad esempio un thread blocca A poi B e un altro blocca B poi A) piuttosto che solo un deadlock che induce, ma finora è stato un grande vantaggio per uno sforzo banale.
Leggi altre domande sui tag unit-testing multithreading