Test delle condizioni di gara multi-thread


54

Leggendo i commenti a questa risposta , in particolare:

Solo perché non puoi scrivere un test non significa che non sia rotto. Comportamento indefinito che di solito funziona come previsto (C e C ++ ne sono pieni), condizioni di gara, potenziale riordino a causa di un modello di memoria debole ... - CodesInChaos 7 ore fa

@CodesInChaos se non può essere riprodotto, il codice scritto in 'fix' non può essere testato. E mettere in vita codice non testato è un crimine peggiore secondo me - RhysW 5 ore fa

... mi chiedo se ci siano buoni modi generali per innescare in modo coerente che si verificano molto raramente nei problemi di produzione causati dalle condizioni di gara nel caso di test.


1
passare attraverso le istruzioni (di montaggio) con le istruzioni su entrambe le estremità
maniaco del cricchetto

1
L'analisi statica può spesso mostrare un potenziale UB, non è chiaro se questo viene conteggiato come test
jk.

Mi dispiace chiedere, ma cosa significa "UB"?
Doug,

2
Bella domanda, sarei interessante nel vedere le potenziali soluzioni a questo.
RhysW,

1
@Doug Undefined Behaviour, che può includere, ma non solo, condizioni di gara
jk.

Risposte:


85

Dopo essere stato in questa pazza impresa dal 1978, dopo aver trascorso quasi tutto quel tempo nel calcolo embedded in tempo reale, lavorando in multitasking, multithread, sistemi multi-qualunque, a volte con più processori fisici, avendo inseguito più della mia giusta quota di razza condizioni, la mia opinione considerata è che la risposta alla tua domanda è abbastanza semplice.

No.

Non c'è un buon modo generale per innescare una condizione di gara nei test.

La tua unica speranza è progettarli completamente dal tuo sistema.

Quando e se scopri che qualcun altro ne ha infilato uno, dovresti picchiarlo fuori da un formicaio e quindi ridisegnarlo per eliminarlo. Dopo aver disegnato il suo passo falso (pronunciato f *** su) fuori dal tuo sistema, puoi andare a liberarlo dalle formiche. (Se le formiche lo hanno già consumato, lasciando solo le ossa, metti un cartello che dice "Questo è quello che succede alle persone che mettono le condizioni di gara nel progetto XYZ!" E LASCIA QUI.)


22
Sono completamente d'accordo. In altre parole, è molto simile allo scherzo. Paziente: "Dottore, fa male quando faccio questo ..." Dottore: "Allora smetti di farlo!"
Mark Rushakoff il

Bella risposta. Se qualcosa provoca un problema non testabile, prova a aggirarlo per cominciare, evita del tutto il problema!
RhysW,

La mia unica domanda è: quanto deve essere grande un formicaio? (+1 BTW).
Peter K.

15
+1 per la corretta pronuncia del passo falso . (E il resto della risposta.)
Blrfl

1
@PeterK., Questo è uno di quei pochi casi nello sviluppo di software, insieme con i monitor, RAM e unità disco, in cui più grande IS meglio.
John R. Strohm,

16

Se sei nella catena di strumenti ms. La ricerca ha creato uno strumento che costringerà nuovi interlevings per ogni corsa e può ricreare le corse fallite, i suoi chiamati scacchi .

ecco un video che lo mostra in uso.


5
Sembra impressionante; Dovrò trovare il tempo di provarlo ad un certo punto.
Dan Neely,

16

Lo strumento migliore che conosco per questo tipo di problemi è un'estensione di Valgrind chiamata Helgrind .

Fondamentalmente Valgrind simula un processore virtuale ed esegue il tuo binario (non modificato) su di esso, in modo che possa controllare ogni singolo accesso alla memoria. Utilizzando tale framework, Helgrind watch system chiama a dedurre quando un accesso a una variabile condivisa non è adeguatamente protetto da un meccanismo di esclusione reciproca. In questo modo è in grado di rilevare una condizione di razza teorica anche se in realtà non è avvenuta.

Intel vende uno strumento molto simile chiamato Intel Inspector .

Questi strumenti offrono grandi risultati ma il programma sarà notevolmente più lento durante l'analisi.


1
Valgrind è ancora uno strumento * nix?
Dan Neely,

1
Sì, Linux, MacOSX, Android e alcuni BSD: valgrind.org/info/platforms.html
Julien

1
ThreadSanitizer è uno strumento simile. Funziona diversamente da Helgrind, il che gli dà il vantaggio di essere molto più veloce, ma richiede l'integrazione nella toolchain.
Sebastian Redl,

7

Per esporre un bug multi-threading è necessario forzare diversi thread di esecuzione per eseguire i loro passi in un particolare ordine interlacciato. Di solito questo è difficile da fare senza il debug manuale o la manipolazione del codice per ottenere una sorta di "handle" per controllare questo interleaving. Ma cambiare il codice che si comporta in modo imprevedibile spesso influenzerà tale imprevedibilità, quindi è difficile da automatizzare.

Un bel trucco è descritto da Jaroslav Tulach in Practical API Design : se hai istruzioni di registrazione nel codice in questione, manipola il consumatore di quelle istruzioni di registrazione (ad esempio uno pseudo-terminale iniettato) in modo che accetti i singoli messaggi di registro in un particolare ordine basato sul loro contenuto. Ciò consente di controllare l'interlacciamento di passaggi in thread diversi senza dover aggiungere al codice di produzione nulla che non sia già presente.


2
Ho fatto qualcosa di simile prima di usare il repository iniettato per mettere in sleep i thread che lo chiamano in ordini specifici per forzare l'interleave che desidero. Avendo scritto il codice che lo fa, sono propenso a +1 alla risposta di John sopra. Scherzi a parte, questa roba è così dolorosa da utilizzare correttamente e offre ancora solo le migliori garanzie di ipotesi perché potrebbero esserci interleave leggermente diverse con risultati diversi; l'approccio migliore consiste nell'eliminare tutte le possibili condizioni di gara attraverso l'analisi statica e l'attenta combinazione di codice per qualsiasi stato condiviso
Jimmy Hoffa,

6

Non c'è modo di essere assolutamente certi che non esistano vari tipi di comportamento indefinito (in particolari condizioni di razza).

Tuttavia, esistono numerosi strumenti che mostrano un buon numero di tali situazioni. Potresti essere in grado di dimostrare che esiste un problema attualmente con tali strumenti, anche se non puoi provare che la tua correzione sia valida.

Alcuni strumenti interessanti per questo scopo:

Valgrind è un controllore di memoria. Trova perdite di memoria, letture di memoria non inizializzata, usi di puntatori penzolanti e accessi fuori limite.

Helgrind è un controllore di sicurezza del thread. Trova le condizioni di gara.

Entrambi funzionano con strumenti dinamici, ovvero prendono il programma così com'è ed eseguono in un ambiente virtualizzato. Questo li rende poco invadenti, ma lenti.

UBSan è un controllo del comportamento indefinito. Trova vari casi di comportamento indefinito in C e C ++, come overflow di interi, spostamenti fuori range e cose simili.

MSan è un controllore di memoria. Ha obiettivi simili a quelli di Valgrind.

TSan è un controllo di sicurezza del thread. Ha obiettivi simili a quelli di Helgrind.

Questi tre sono integrati nel compilatore Clang e generano codice in fase di compilazione. Ciò significa che devi integrarli nel tuo processo di compilazione (in particolare, devi compilare con Clang), il che li rende molto più difficili da impostare inizialmente rispetto a * grind, ma d'altra parte hanno un tempo di esecuzione molto inferiore.

Tutti gli strumenti che ho elencato funzionano su Linux e alcuni su MacOS. Non credo che nessun lavoro su Windows sia ancora affidabile.


1

Sembra che la maggior parte delle risposte qui confonda questa domanda come "come posso rilevare automaticamente le condizioni di gara?" quando la domanda è davvero "come posso riprodurre le condizioni di gara nei test quando le trovo?"

Il modo per farlo è introdurre la sincronizzazione nel codice utilizzato solo per i test. Ad esempio, se si verifica una condizione di competizione quando si verifica l'evento X tra l'evento A e l'evento B, quindi per testare l'applicazione, scrivere un codice che attende che si verifichi l'evento X dopo l'evento A. Probabilmente avrai bisogno di un modo perché i tuoi test parlino con la tua applicazione per dirlo ("hey sto testando questa cosa, quindi aspetta questo evento in questa posizione").

Sto usando node.js e mongo, dove alcune azioni prevedono la creazione di dati coerenti in più raccolte. In questi casi, i test delle mie unità effettueranno una chiamata all'applicazione per dirle "imposta un'attesa per l'evento X", e una volta che l'applicazione lo ha impostato, verrà eseguito il test per l'evento X e i test successivamente diranno l'applicazione ("ho finito con l'attesa per l'evento X"), quindi il resto dei test verrà eseguito normalmente.

La risposta qui spiega questo tipo di cose in dettaglio nel contesto di Python: https://stackoverflow.com/questions/19602535/how-can-i-reproduce-the-race-conditions-in-this-python-code- affidabile

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.