Afflitto da bug multithread


26

Nel mio nuovo team che gestisco, la maggior parte del nostro codice è costituito da piattaforma, socket TCP e codice di rete http. Tutto C ++. La maggior parte proviene da altri sviluppatori che hanno lasciato il team. Gli attuali sviluppatori del team sono molto intelligenti, ma per lo più junior in termini di esperienza.

Il nostro problema più grande: bug di concorrenza multi-thread. La maggior parte delle nostre librerie di classi sono scritte in modo asincrono mediante l'uso di alcune classi di pool di thread. I metodi sulle librerie di classi spesso accodano taks di lunga durata nel pool di thread da un thread e quindi i metodi di callback di quella classe vengono richiamati su un thread diverso. Di conseguenza, abbiamo molti bug di edge case che implicano ipotesi di threading errate. Ciò si traduce in bug sottili che vanno oltre la semplice presenza di sezioni e blocchi critici per evitare problemi di concorrenza.

Ciò che rende ancora più difficili questi problemi è che i tentativi di risoluzione sono spesso errati. Alcuni errori che ho riscontrato nel tentativo del team (o all'interno del codice legacy stesso) includono qualcosa di simile al seguente:

Errore comune n. 1 - Risolvere il problema di concorrenza semplicemente bloccando i dati condivisi, ma dimenticando cosa succede quando i metodi non vengono chiamati in un ordine previsto. Ecco un esempio molto semplice:

void Foo::OnHttpRequestComplete(statuscode status)
{
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

Quindi ora abbiamo un bug in cui Shutdown potrebbe essere chiamato mentre OnHttpNetworkRequestComplete si sta verificando. Un tester trova il bug, acquisisce il crash dump e assegna il bug a uno sviluppatore. A sua volta risolve il bug in questo modo.

void Foo::OnHttpRequestComplete(statuscode status)
{
    AutoLock lock(m_cs);
    m_pBar->DoSomethingImportant(status);
}

void Foo::Shutdown()
{
    AutoLock lock(m_cs);
    m_pBar->Cleanup();
    delete m_pBar;
    m_pBar=nullptr;
}

La correzione di cui sopra sembra buona finché non ti rendi conto che c'è un caso ancora più sottile. Cosa succede se Shutdown viene chiamato prima che OnHttpRequestComplete venga richiamato? Gli esempi del mondo reale del mio team sono ancora più complessi e i casi limite sono ancora più difficili da individuare durante il processo di revisione del codice.

Errore comune n. 2 : risolvere i problemi di deadlock uscendo alla cieca dal blocco, attendere il completamento dell'altro thread, quindi immettere nuovamente il blocco, ma senza gestire il caso in cui l'oggetto è stato appena aggiornato dall'altro thread!

Errore comune n. 3 - Anche se gli oggetti vengono contati come riferimento, la sequenza di arresto "rilascia" è puntatore. Ma dimentica di aspettare che il thread ancora in esecuzione rilasci la sua istanza. Pertanto, i componenti vengono arrestati in modo pulito, quindi vengono richiamati richiami spuri o in ritardo su un oggetto in uno stato che non prevede ulteriori chiamate.

Esistono altri casi limite, ma la linea di fondo è questa:

La programmazione multithread è semplicemente dura, anche per le persone intelligenti.

Mentre colgo questi errori, passo il tempo a discutere degli errori con ogni sviluppatore nello sviluppo di una soluzione più appropriata. Ma sospetto che siano spesso confusi su come risolvere ogni problema a causa dell'enorme quantità di codice legacy che la correzione "giusta" comporterà il contatto.

Presto spediremo e sono sicuro che le patch che applicheremo saranno valide per la prossima versione. Successivamente, avremo del tempo per migliorare la base di codice e il refactor dove necessario. Non avremo il tempo di riscrivere tutto. E la maggior parte del codice non è poi così male. Ma sto cercando di refactificare il codice in modo tale da evitare del tutto i problemi di threading.

Un approccio che sto prendendo in considerazione è questo. Per ogni significativa funzionalità della piattaforma, disporre di un singolo thread dedicato in cui vengono raggruppati tutti gli eventi e le richiamate di rete. Simile al threading apartment COM in Windows con l'uso di un loop di messaggi. Le operazioni di blocco lunghe potrebbero comunque essere inviate a un thread del pool di lavoro, ma il callback di completamento viene richiamato sul thread del componente. I componenti potrebbero anche condividere lo stesso thread. Quindi tutte le librerie di classi in esecuzione all'interno del thread possono essere scritte partendo dal presupposto di un singolo mondo thread.

Prima di intraprendere questa strada, sono anche molto interessato alla presenza di altre tecniche standard o modelli di progettazione per affrontare problemi multithread. E devo sottolineare - qualcosa al di là di un libro che descrive le basi dei mutex e dei semafori. Cosa pensi?

Sono anche interessato a qualsiasi altro approccio da adottare per un processo di refactoring. Incluso uno dei seguenti:

  1. Letteratura o documenti sui modelli di progettazione intorno ai fili. Qualcosa oltre un'introduzione a mutex e semafori. Non abbiamo nemmeno bisogno di un parallelismo massiccio, ma solo modi per progettare un modello a oggetti in modo da gestire correttamente eventi asincroni da altri thread .

  2. Modi per tracciare il diagramma della filettatura di vari componenti, in modo che sia facile studiare e sviluppare soluzioni per. (Cioè, un equivalente UML per discutere discussioni tra oggetti e classi)

  3. Educare il team di sviluppo sui problemi con il codice multithread.

  4. Cosa faresti?


23
Alcune persone di fronte a un problema pensano che userò il multi threading. Ora hanno due sonde
Tom Squires

20
ciò che funziona bene per me è sbarazzarsi della mutabilità quando possibile. Quando vedo che l' oggetto mutabile cambia stato per passare il nuovo valore, provo a rifattarlo nel passare un nuovo oggetto immutabile che tiene invece il valore modificato. Se l'inizializzazione degli oggetti viene eseguita in modo sicuro, ciò garantisce l'assenza di corse di dati - un
vero

3
Benvenuti all'inferno multi-thread. Scrivo programmi multi-thread / paralell da> 20 anni, in Ada, Occam, C ++. Non è mai facile, tutto richiede un pensiero molto attento e chiunque dica "è facile basta fare X" è uno sciocco che non capisce davvero cosa sta succedendo. In bocca al lupo.
quick_now

2
Se vuoi vedere la concorrenza fatta bene usa Erlang! In verità quello che vuoi è una forma di modello di attore che non ha nulla in comune, dove verranno eliminati gli strani casi angolari.
Zaccaria K,

3
@DeadMG Direi che la concorrenza dello stato condiviso è intrinsecamente soggetta a strani casi angolari e dovrebbe essere evitata. Ma Hey, ho scritto un libro su Erlang
Zachary K,

Risposte:


27

Il tuo codice ha altri importanti problemi oltre a questo. Eliminazione manuale di un puntatore? Chiamare una cleanupfunzione? Owch. Inoltre, come sottolineato con precisione nel commento alla domanda, non usi RAII per il tuo lock, che è un altro fallimento abbastanza epico e garantisce che quando si DoSomethingImportantgenera un'eccezione, accadono cose terribili.

Il fatto che si verifichi questo bug multithread è solo un sintomo del problema principale: il tuo codice ha una semantica estremamente cattiva in qualsiasi situazione di threading e stai usando strumenti ed ex idiomi completamente inaffidabili. Se fossi in te, sarei stupito che funzioni con un singolo thread, figuriamoci di più.

Errore comune n. 3 - Anche se gli oggetti vengono contati come riferimento, la sequenza di arresto "rilascia" è puntatore. Ma dimentica di aspettare che il thread ancora in esecuzione rilasci la sua istanza. Pertanto, i componenti vengono arrestati in modo pulito, quindi vengono richiamati richiami spuri o in ritardo su un oggetto in uno stato che non prevede ulteriori chiamate.

L'intero conteggio dei riferimenti è che il thread ha già rilasciato la sua istanza . Perché in caso contrario, non può essere distrutto perché il thread ha ancora un riferimento.

Usa std::shared_ptr. Quando tutte le discussioni hanno rilasciato (e nessuno , quindi, può essere il richiamo della funzione, in quanto non hanno puntatore ad esso), allora il distruttore è chiamato. Questo è garantito sicuro.

In secondo luogo, utilizzare una vera libreria di threading, come Intel Thread Building Blocks o Microsoft Parallel Patterns Library. Scrivere il tuo richiede tempo e non è affidabile e il tuo codice è pieno di dettagli di threading di cui non ha bisogno. Fare i tuoi blocchi è altrettanto male che fare la tua gestione della memoria. Hanno già implementato molti idiomi di threading molto utili per scopi generici che funzionano correttamente per il tuo uso.


Questa è una risposta ok, ma non la direzione che stavo cercando, perché passa troppo tempo a valutare un pezzo di codice di esempio che è stato scritto solo per semplicità (e non riflette il nostro vero codice nel nostro prodotto). Ma sono curioso di sapere un tuo commento: "strumenti inaffidabili". Che cos'è uno strumento inaffidabile? Quali strumenti mi consigliate?
koncurrency

5
@koncurrency: uno strumento inaffidabile è una cosa come la gestione manuale della memoria o la scrittura della propria sincronizzazione in cui in teoria risolve un problema X ma in realtà è così grave che puoi praticamente garantire errori giganti e l'unico modo in cui potrebbe eventualmente risolvere il problema a portata di mano su una scala ragionevole è l'investimento massiccio e sproporzionato del tempo degli sviluppatori, che è quello che hai adesso.
DeadMG

9

Altri poster hanno commentato bene cosa si dovrebbe fare per risolvere i problemi fondamentali. Questo post riguarda il problema più immediato di correggere il codice legacy abbastanza bene da farti guadagnare tempo per rifare tutto nel modo giusto. In altre parole, questo non è il modo giusto di fare le cose, è solo un modo per zoppicare per ora.

La tua idea di consolidare eventi chiave è un buon inizio. Andrei al punto di utilizzare un singolo thread di invio per gestire tutti gli eventi di sincronizzazione delle chiavi, ovunque vi sia dipendenza dall'ordine. Installa una coda di messaggi thread-safe e ovunque esegui operazioni sensibili alla concorrenza (allocazioni, cleanup, callback, ecc.), Invece invia un messaggio a quel thread e fallo eseguire o attivare l'operazione. L'idea è che questo thread controlla tutti gli avviamenti, gli arresti, le allocazioni e le pulizie dell'unità di lavoro.

Il thread di invio non risolve i problemi descritti, li consolida in un unico punto. Devi ancora preoccuparti di eventi / messaggi che si verificano in ordine imprevisto. Gli eventi con runtime significativi dovranno comunque essere inviati ad altri thread, quindi ci sono ancora problemi con la concorrenza sui dati condivisi. Un modo per mitigare è quello di evitare il passaggio di dati per riferimento. Ove possibile, i dati nei messaggi di spedizione devono essere copie che saranno di proprietà del destinatario. (Questo è sulla falsariga di rendere immutabili i dati che altri hanno menzionato.)

Il vantaggio di questo approccio di spedizione è che all'interno del thread di spedizione hai una sorta di rifugio sicuro in cui almeno sai che determinate operazioni avvengono in sequenza. Lo svantaggio è che crea un collo di bottiglia e un sovraccarico di CPU aggiuntivo. Suggerisco di non preoccuparsi di nessuna di queste cose all'inizio: concentrati sull'ottenere prima una certa misura del corretto funzionamento spostandoti il ​​più possibile nel thread di invio. Quindi esegui un po 'di profilazione per vedere cosa sta impiegando più tempo della CPU e inizia a spostarlo indietro dal thread di invio utilizzando le tecniche di multithreading corrette.

Ancora una volta, quello che sto descrivendo non è il modo giusto di fare le cose, ma è un processo che può spostarti verso il modo giusto con incrementi abbastanza piccoli da rispettare le scadenze commerciali.


+1 per un suggerimento ragionevole e intermedio su come superare la sfida esistente.

Sì, questo è l'approccio che sto studiando. Sollevi buoni punti sulle prestazioni.
koncurrency

Cambiare le cose per passare attraverso un singolo thread di invio non suona come una patch veloce, ma piuttosto per me un grande refactoring.
Sebastian Redl,

8

Sulla base del codice mostrato, hai una pila di WTF. È estremamente difficile, se non impossibile, correggere in modo incrementale un'applicazione multi-thread scritta male. Informa i proprietari che l'applicazione non sarà mai affidabile senza una significativa modifica. Fornisci loro una stima basata sull'ispezione e la rielaborazione di ogni bit del codice che interagisce con gli oggetti condivisi. Prima di tutto dai un preventivo per l'ispezione. Quindi puoi dare un preventivo per la rilavorazione.

Quando si rielabora il codice, è necessario pianificare di scrivere il codice in modo che sia dimostrabilmente corretto. Se non sai come farlo, trova qualcuno che lo fa o finirai nello stesso posto.


Leggilo ora, dopo che la mia risposta è stata votata. Volevo solo dire che adoro la frase introduttiva :)
back2dos

7

Se hai del tempo da dedicare al refactoring della tua candidatura, ti consiglio di dare un'occhiata al modello dell'attore (vedi ad esempio Theron , Casablanca , libcppa , CAF per le implementazioni C ++).

Gli attori sono oggetti che vengono eseguiti contemporaneamente e comunicano tra loro solo utilizzando lo scambio di messaggi asincrono. Quindi, tutti i problemi di gestione dei thread, mutex, deadlock, ecc., Sono gestiti da una libreria di implementazione degli attori e puoi concentrarti sull'implementazione del comportamento dei tuoi oggetti (attori), che si riduce a ripetere il ciclo

  1. Ricevi un messaggio
  2. Esegui il calcolo
  3. Invia messaggio / i / crea / uccidi altri attori.

Un approccio per te potrebbe essere quello di fare prima qualche lettura sull'argomento e possibilmente dare un'occhiata a una o due librerie per vedere se il modello dell'attore può essere integrato nel tuo codice.

Sto usando (una versione semplificata di) questo modello in un mio progetto ormai da alcuni mesi e sono sorpreso da quanto sia robusto.


1
La biblioteca di Akka per Scala è una bella implementazione di ciò che pensa molto a come uccidere gli attori genitori quando i bambini muoiono, o viceversa. So che non è C ++, ma vale la pena dare un'occhiata: akka.io
GlenPeterson,

1
@GlenPeterson: Grazie, conosco Akka (che al momento considero la soluzione più interessante, e funziona sia con Java che con Scala) ma la domanda riguarda specificamente il C ++. Altrimenti si potrebbe considerare Erlang. Immagino che a Erlang tutti i mal di testa della programmazione multi-threading siano andati per sempre. Ma forse framework come Akka si avvicinano molto.
Giorgio,

"Suppongo che a Erlang tutti i mal di testa della programmazione multi-threading siano spariti per sempre." Penso che forse questo sia un po 'sopravvalutato. O se fosse vero, le prestazioni potrebbero essere carenti. So che Akka non funziona con il C ++, dicendo solo che sembra all'avanguardia per la gestione di più thread. Tuttavia, non è sicuro per i thread. Puoi ancora passare lo stato mutevole tra gli attori e spararti ai piedi.
GlenPeterson,

Non sono un esperto di Erlang ma AFAIK ogni attore viene eseguito in modo isolato e vengono scambiati messaggi immutabili. Quindi non devi assolutamente avere a che fare con i thread e lo stato mutevole condiviso. Le prestazioni sono probabilmente inferiori a C ++, ma ciò accade sempre quando si aumenta il livello di astrazione (si aumenta il tempo di esecuzione ma si riducono i tempi di sviluppo).
Giorgio,

Il downvoter può lasciare un commento e suggerire come posso migliorare questa risposta?
Giorgio,

6

Errore comune n. 1 - Risolvere il problema di concorrenza semplicemente bloccando i dati condivisi, ma dimenticando cosa succede quando i metodi non vengono chiamati in un ordine previsto. Ecco un esempio molto semplice:

L'errore qui non è il "dimenticare", ma il "non risolverlo". Se si verificano eventi in un ordine imprevisto, si verifica un problema. Dovresti risolverlo invece di provare a aggirarlo (schiaffeggiare un lucchetto su qualcosa di solito è un aggancio).

Dovresti cercare di adattare il modello / messaggistica dell'attore a un certo livello e di separare le preoccupazioni. Il ruolo di Fooè chiaramente quello di gestire un qualche tipo di comunicazione HTTP. Se vuoi progettare il tuo sistema per farlo in parallelo, è il livello sopra che deve gestire i cicli di vita degli oggetti e accedere alla sincronizzazione di conseguenza.

Cercare di far funzionare più thread sugli stessi dati mutabili è difficile. Ma è anche raramente necessario. Tutti i casi comuni che lo richiedono, sono già stati astratti in concetti più gestibili e implementati più volte per qualsiasi linguaggio imperativo importante. Devi solo usarli.


2

I tuoi problemi sono piuttosto gravi, ma tipici del cattivo uso del C ++. La revisione del codice risolverà alcuni di questi problemi. 30 minuti, una serie di bulbi oculari produce il 90% dei risultati (la citazione è googleable)

# 1 Problema È necessario assicurarsi che sia presente una rigorosa gerarchia di blocchi per impedire il blocco dei deadlock.

Se si sostituisce Autolock con un wrapper e una macro, è possibile farlo.

Mantieni una mappa globale statica di blocchi creati nella parte posteriore del wrapper. Utilizzare una macro per inserire le informazioni sul nome e il numero di riga nel costruttore del wrapper Autolock.

Avrai anche bisogno di un grafico a dominatore statico.

Ora all'interno di lock devi aggiornare il grafico del dominatore, e se ricevi una modifica all'ordine affermi un errore e interrompi.

Dopo test approfonditi potresti liberarti della maggior parte dei deadlock latenti.

Il codice viene lasciato come esercizio per lo studente.

Il problema n. 2 andrà quindi via (principalmente)

La tua soluzione architettonica funzionerà. L'ho già usato in missione e nei sistemi critici della vita. La mia opinione è questa

  • Passa oggetti immutabili o copia di essi prima di passare.
  • Non condividere i dati tramite variabili pubbliche o getter.

  • Gli eventi esterni arrivano tramite una spedizione multithread in una coda gestita da un thread. Ora puoi una specie di ragione sulla gestione degli eventi.

  • Le modifiche ai dati che attraversano thread entrano in un qeuue thread-safe, vengono gestite da un thread. Fai abbonamenti. Ora puoi una specie di ragione sui flussi di dati.

  • Se i tuoi dati devono essere trasferiti in diverse città, pubblicali nella coda dei dati. Ciò lo copierà e lo passerà agli abbonati in modo asincrono. Inoltre interrompe tutte le dipendenze dei dati nel programma.

Questo è praticamente un modello di attore a buon mercato. I link di Giorgio aiuteranno.

Infine, il tuo problema con gli oggetti arrestati.

Quando stai contando i riferimenti, hai risolto il 50%. L'altro 50% è riferirsi al conteggio delle richiamate. Passa un referente ai possessori di callback. La chiamata di arresto deve quindi attendere il conteggio zero sul refcount. Non risolve grafici di oggetti complicati; sta entrando nella vera raccolta dei rifiuti. (Qual è la motivazione in Java per non fare alcuna promessa su quando o se verrà finalizzato (); per farti uscire dalla programmazione in quel modo.)


2

Per i futuri esploratori: per integrare la risposta sul modello di attore vorrei aggiungere CSP ( comunicazione di processi sequenziali ), con un cenno alla più ampia famiglia di calcoli di processo in cui si trova. CSP è simile al modello di attore, ma suddiviso in modo diverso. Hai ancora un sacco di thread, ma comunicano attraverso canali specifici, piuttosto che specificamente l'uno con l'altro, ed entrambi i processi devono essere pronti rispettivamente a inviare e ricevere prima che si verifichino. C'è anche un linguaggio formalizzato per dimostrare il codice CSP corretto. Sto ancora passando all'utilizzo pesante di CSP, ma lo sto usando in alcuni progetti da alcuni mesi, ora, ed è molto semplificato.

L'Università del Kent ha un'implementazione C ++ ( https://www.cs.kent.ac.uk/projects/ofa/c++csp/ , clonato su https://github.com/themasterchef/cppcsp2 ).


1

Letteratura o documenti sui modelli di progettazione intorno ai fili. Qualcosa oltre un'introduzione a mutex e semafori. Non abbiamo nemmeno bisogno di un parallelismo massiccio, ma solo modi per progettare un modello a oggetti in modo da gestire correttamente eventi asincroni da altri thread.

Attualmente sto leggendo questo e spiega tutti i problemi che puoi avere e come evitarli, in C ++ (usando la nuova libreria di threading ma penso che le spiegazioni globali siano valide per il tuo caso): http: //www.amazon. com / C-concorrenza-Azione-pratico-Multithreading / dp / 1933988770 / ref = sr_1_1? ie = UTF8 & qid = 1.337.934,534 mila & sr = 8-1

Modi per tracciare il diagramma della filettatura di vari componenti, in modo che sia facile studiare e sviluppare soluzioni per. (Cioè, un equivalente UML per discutere discussioni tra oggetti e classi)

Personalmente uso un UML semplificato e presumo solo che i messaggi vengano eseguiti in modo asincrono. Inoltre, questo è vero tra i "moduli" ma all'interno dei moduli che non voglio conoscere.

Educare il team di sviluppo sui problemi con il codice multithread.

Il libro sarebbe di aiuto, ma penso che esercizi / prototipazione e mentore con esperienza sarebbero migliori.

Cosa faresti?

Eviterei totalmente che la gente non capisca i problemi di concorrenza al lavoro sul progetto. Ma immagino che tu non possa farlo, quindi nel tuo caso specifico, oltre a cercare di assicurarmi che il team sia più istruito, non ne ho idea.


Grazie per il suggerimento sul libro. Probabilmente lo prenderò.
koncurrency

Il threading è davvero difficile. Non tutti i programmatori sono all'altezza della sfida. Nel mondo degli affari, ogni volta che vedevo i thread utilizzati, erano circondati da lucchetti in modo tale che due thread non potevano essere eseguiti contemporaneamente. Ci sono regole che puoi seguire per renderlo più semplice, ma è ancora difficile.
GlenPeterson,

@GlenPeterson D'accordo, ora che ho più esperienza (da questa risposta) trovo che abbiamo bisogno di astrazioni migliori per renderlo gestibile e scoraggiare la condivisione dei dati. Fortunatamente, i progettisti del linguaggio sembrano lavorare sodo su questo.
Klaim,

Sono stato davvero colpito da Scala, in particolare per aver apportato vantaggi di programmazione funzionale di immutabilità, effetti collaterali minimi a Java, che è un diretto discendente del C ++. Funziona su Java Virtual Machine, quindi potrebbe non avere le prestazioni necessarie. Il libro di Joshua Bloch, "Effective Java" è incentrato sulla riduzione al minimo della mutabilità, sulla realizzazione di interfacce ermetiche e sulla sicurezza dei thread. Anche se è basato su Java, scommetto che potresti applicarne l'80-90% su C ++. Mettere in discussione la mutabilità e lo stato condiviso (o la mutabilità dello stato condiviso) nelle revisioni del codice potrebbe essere un buon primo passo per te.
GlenPeterson,

1

Sei già sulla strada riconoscendo il problema e attivamente cercando una soluzione. Ecco cosa farei:

  • Siediti e progetta un modello di threading per la tua applicazione. Questo è un documento che risponde a domande come: quali tipi di thread hai? Quali cose dovrebbero essere fatte in quale thread? Quali diversi tipi di pattern di sincronizzazione dovresti usare? In altre parole, dovrebbe descrivere le "regole di ingaggio" quando si combattono i problemi del multithreading.
  • Utilizzare gli strumenti di analisi dei thread per verificare la presenza di errori nella base di codice. Valgrind ha un controllo thread chiamato Helgrind che è bravo a individuare cose come lo stato condiviso manipolato senza una corretta sincronizzazione. Ci sono sicuramente altri buoni strumenti là fuori, vai a cercarli.
  • Prendi in considerazione la migrazione da C ++. Il C ++ è un incubo in cui scrivere programmi simultanei. La mia scelta personale sarebbe Erlang , ma è una questione di gusti.

8
Sicuramente -1 per l'ultimo bit. Sembrerebbe che il codice dell'OP stia utilizzando gli strumenti più primitivi e non gli strumenti C ++ effettivi.
DeadMG

2
Non sono d'accordo La concorrenza in C ++ è un incubo anche se si utilizzano i meccanismi e gli strumenti C ++ appropriati. E per favore nota che ho scelto la frase " considera ". Capisco perfettamente che potrebbe non essere un'alternativa realistica, ma stare con C ++ senza considerare le alternative è semplicemente stupido.
JesperE

4
@JesperE - scusa, ma no. La concorrenza in C ++ è solo un incubo se lo fai andando a un livello troppo basso. Usa una corretta astrazione di threading e non è peggio di qualsiasi altra lingua o runtime. E con la corretta struttura dell'applicazione, in realtà è abbastanza facile come qualsiasi altra cosa che abbia mai visto.
Michael Kohne,

2
Dove lavoro credo che abbiamo una struttura applicativa adeguata, utilizziamo le astrazioni di threading corrette e così via. Nonostante ciò, nel corso degli anni abbiamo trascorso innumerevoli ore nel debug di bug che semplicemente non apparivano in lingue progettate correttamente per la concorrenza. Ma ho la sensazione che dovremo concordare di non essere d'accordo su questo.
JesperE

1
@JesperE: sono d'accordo con te. Il modello Erlang (per il quale esistono implementazioni per Scala / Java, Ruby e, per quanto ne so, anche per C ++) è molto più robusto della codifica diretta con i thread.
Giorgio,

1

Guardando il tuo esempio: non appena Foo :: Shutdown inizia l'esecuzione, non deve essere possibile chiamare OnHttpRequestComplete per l'esecuzione. Non ha nulla a che fare con nessuna implementazione, semplicemente non può funzionare.

Si potrebbe anche sostenere che Foo :: Shutdown non dovrebbe essere richiamabile mentre è in esecuzione una chiamata a OnHttpRequestComplete (sicuramente vera) e probabilmente non se una chiamata a OnHttpRequestComplete è ancora in sospeso.

La prima cosa da fare non è bloccare ecc. Ma la logica di ciò che è permesso o no. Un semplice modello potrebbe essere che la tua classe potrebbe avere zero o più richieste incomplete, zero o più completamenti che non sono stati ancora chiamati, zero o più completamenti in esecuzione e che il tuo oggetto vuole arrestarsi o meno.

Foo :: Shutdown dovrebbe terminare l'esecuzione di completamenti, eseguire richieste incomplete al punto in cui possono essere arrestate, se possibile, per non consentire l'avvio di ulteriori completamenti, per non consentire l'avvio di più richieste.

Cosa devi fare: aggiungi le specifiche alle tue funzioni dicendo esattamente cosa faranno. (Ad esempio, l'avvio di una richiesta http potrebbe non riuscire dopo la chiamata dell'arresto di arresto). E quindi scrivi le tue funzioni in modo che soddisfino le specifiche.

I blocchi vengono utilizzati al meglio solo per il minor tempo possibile per controllare la modifica delle variabili condivise. Quindi potresti avere una variabile "performingShutDown" che è protetta da un Lock.


0

Cosa faresti?

Ad essere onesti; Scapperei via velocemente.

I problemi di concorrenza sono NASTY . Qualcosa può funzionare perfettamente per mesi e poi (a causa della tempistica specifica di diverse cose) improvvisamente esplodere in faccia al cliente, senza alcun modo per capire cosa è successo, nessuna speranza di vedere mai un bel bug report (riproducibile) e nessun modo per essere sicuri, non era un problema tecnico hardware che non ha nulla a che fare con il software.

Evitare problemi di concorrenza deve iniziare durante la fase di progettazione, iniziando esattamente da come lo farai ("ordine globale di blocco", modello dell'attore, ...). Non è qualcosa che cerchi di risolvere in un panico folle nella speranza che tutto non si autodistrugga dopo una prossima uscita.

Nota che non sto scherzando qui. Le tue parole (" La maggior parte ha avuto origine da altri sviluppatori che hanno lasciato il team. Gli attuali sviluppatori del team sono molto intelligenti, ma per lo più junior in termini di esperienza ") indicano che tutta l'esperienza delle persone ha già fatto ciò che io sto suggerendo.

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.