Concorrenza: come affrontare la progettazione e eseguire il debug dell'implementazione?


37

Sto sviluppando sistemi concorrenti da diversi anni e ho una buona conoscenza della materia nonostante la mia mancanza di formazione formale (cioè nessuna laurea). Ci sono alcune nuove lingue che sono diventate popolari di cui parlare di recente, progettate per facilitare la concorrenza come Erlang e Go. Sembra che il loro approccio alla concorrenza faccia eco alla mia esperienza su come rendere i sistemi scalabili e sfruttare più core / processori / macchine.

Tuttavia, trovo che ci sono pochissimi strumenti per aiutare a visualizzare ciò che intendi fare e per verificare che tu sia almeno vicino alla tua visione originale. Il debug del codice simultaneo può essere un incubo con linguaggi non progettati per la concorrenza (come C / C ++, C #, Java, ecc.). In particolare, può essere quasi impossibile ricreare condizioni che si verificano facilmente su un sistema nel proprio ambiente di sviluppo.

Quindi, quali sono i tuoi approcci alla progettazione di un sistema per gestire la concorrenza e l'elaborazione parallela? Esempi:

  • Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?
  • Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?
  • Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Ho le mie risposte per alcuni di questi, ma vorrei anche imparare un po 'di più.

modificare

Finora abbiamo molti buoni input. Molti degli articoli collegati sono molto buoni e ne ho già letti alcuni.

La mia esperienza personale con la programmazione concorrente mi porta a credere che abbiate bisogno di una mentalità diversa rispetto a quella con la programmazione sequenziale. Il divario mentale è probabilmente ampio quanto la differenza tra programmazione orientata agli oggetti e programmazione procedurale. Vorrei che questo insieme di domande si concentrasse maggiormente sui processi di pensiero necessari (cioè la teoria) per avvicinarsi sistematicamente alle risposte. Quando fornisci risposte più concrete, aiuta a fornire un esempio - qualcosa che hai passato personalmente.

Obiettivo per la taglia

Non dirmi cosa dovrei fare. L'ho già sotto controllo. Dimmi cosa fai. Dimmi come si risolvere questi problemi.


Questa è una buona domanda: molta profondità possibile. Ho anche avuto una buona esperienza con le applicazioni multi-thread in Java, ma vorrei saperne di più.
Michael K,

Finora abbiamo alcune buone risposte. Qualcuno vuole avventurarsi in quello che vorresti avere per aiutarti in questo settore?
Berin Loritsch,

TotalView Debugger per la codifica simultanea è uno strumento piuttosto utile, ma prende un po 'di una curva di apprendimento - totalviewtech.com/products/totalview.html
Fanatic23

Forse la registrazione potrebbe aiutarti con le ultime due domande.
Amir Rezaei,

Quello che sto cercando sono i processi delle persone. Queste sono aree in cui gli strumenti che ho usato sono inadeguati, ma possono fare il lavoro. Sono meno preoccupato di citare l'articolo di qualcun altro e più preoccupato per la metodologia qui.
Berin Loritsch,

Risposte:


11

Sto sviluppando sistemi concorrenti da diversi anni e ho una buona conoscenza della materia, nonostante la mia mancanza di formazione formale (cioè nessuna laurea).

Molti dei migliori programmatori che conosco non hanno finito l'Università. Per quanto mi riguarda ho studiato filosofia.

C / C ++, C #, Java, ecc.). In particolare, può essere quasi impossibile ricreare condizioni che si verificano facilmente su un sistema nel proprio ambiente di sviluppo.

Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?

di solito iniziamo con una metafora alta 1000 miglia per chiarire la nostra architettura a noi stessi (in primo luogo) e agli altri (in secondo luogo).

Quando abbiamo affrontato questo problema, abbiamo sempre trovato un modo per limitare la visibilità degli oggetti concorrenti a quelli non concorrenti.

Ultimamente ho scoperto gli attori in scala e ho visto che le mie vecchie soluzioni erano una sorta di "miniactor", molto meno potenti di quelle di scala. Quindi il mio suggerimento è di iniziare da lì.

Un altro suggerimento è quello di saltare il maggior numero possibile di problemi: ad esempio usiamo la cache centralizzata (terracotta) invece di mantenere le mappe in memoria, usando callback della classe interna invece di metodi sincronizzati, inviando messaggi invece di scrivere memoria condivisa ecc.

Con scala è comunque molto più semplice.

Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Nessuna vera risposta qui. Abbiamo alcuni unit test per la concorrenza e disponiamo di una suite di test di carico per sollecitare l'applicazione il più possibile.

Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Ancora una volta nessuna vera risposta: progettiamo la nostra metafora sulla lavagna e cerchiamo di assicurarci che non ci siano conflitti sul lato architettonico.

Per Arch qui intendo la definizione di Neal Ford: Sw Architecture è tutto ciò che sarà molto difficile da cambiare in seguito.

la programmazione mi porta a credere che hai bisogno di una mentalità diversa rispetto a quella con la programmazione sequenziale.

Forse, ma per me è semplicemente impossibile pensare in modo parallelo, quindi progettare meglio il nostro software in un modo che non richiede pensiero parallelo e con guardrail chiari per evitare arresti anomali tra le corsie di concorrenza.


6

Per me è tutto sui dati. Rompere i dati nel modo giusto e l'elaborazione parallela è semplice. Tutti i problemi con ritenzione, deadlock e così via.

So che questo non è l'unico modo per parallelizzare, ma per me è di gran lunga il più utile.

Per illustrare una storia (non così rapida):

Ho lavorato su un grande sistema finanziario (controllo di borsa) dal 2007 al 2009 e il volume di elaborazione dei dati è stato molto grande. Per illustrare, tutti i calcoli effettuati su 1 singolo account di un client hanno impiegato circa 1 ~ 3 secondi sulla loro workstation media e c'erano più di 30k account. Ogni notte, chiudere il sistema era un grosso problema per gli utenti (di solito più di 6 ore di elaborazione, senza alcun margine di errore per loro).

Lo studio del problema ha ulteriormente rivelato che potremmo paralelizzare i calcoli tra diversi computer, ma avremmo ancora un enorme collo di bottiglia sul vecchio server di database (un server SQL 2000 che emula SQL 6.5).

Era abbastanza chiaro che il nostro pacchetto di elaborazione minimo era il calcolo di un singolo account, e il principale collo di bottiglia era la conservazione del server di database (potevamo vedere sulle diverse connessioni di "sp_who" in attesa di fare la stessa elaborazione). Quindi il processo parallelo è andato così:

1) Un singolo produttore, responsabile della lettura del database o della sua scrittura, in sequenza. Nessuna concorrenza consentita qui. Il produttore ha preparato una fila di lavori, per i consumatori. Il database apparteneva esclusivamente a questo produttore.

2) Diversi consumatori, su più macchine. Ciascuno dei consumatori ha ricevuto un intero pacchetto di dati, dalla coda, pronti per il calcolo. Ogni operazione di deqeue è sincronizzata.

3) Dopo il calcolo, ciascun consumatore ha rispedito i dati in una coda sincronizzata in memoria al produttore, al fine di conservare i dati.

C'erano diversi punti di controllo, diversi meccanismi per assicurare che le transazioni fossero correttamente salvate (nessuna era lasciata indietro), ma l'intero lavoro ne valeva la pena. Alla fine, i calcoli distribuiti su 10 computer (più il produttore / computer in coda) hanno portato il tempo di chiusura dell'intero sistema a 15 minuti.

Il solo fatto di eliminare i problemi di conservazione causati dalla cattiva gestione della concorrenza SQL 6.5 ci ha dato un grande vantaggio. Il resto era praticamente lineare, ogni nuovo computer aggiunto alla "griglia" ha ridotto i tempi di elaborazione, fino a quando non abbiamo raggiunto la "massima efficienza" delle operazioni sequenziali di lettura / scrittura sul database.


2

Lavorare in un ambiente multi-threading è difficile e richiede la disciplina del codice. Devi seguire le linee guida appropriate per prendere il blocco, rilasciare il blocco, accedere alle variabili globali ecc.

Vorrei provare a rispondere alla tua domanda uno a uno

* How do you figure out what can be made concurrent vs. what has to be sequential?

Usa la concorrenza per

1) Polling: - è necessario un thread per eseguire continuamente il polling di qualcosa o inviare l'aggiornamento su base regolare. (Concetti come heart-bit, che inviano alcuni dati a intervalli regolari al server centrale per dire che sono vivo.)

2) Le operazioni con I / O pesanti potrebbero essere eseguite in parallelo. Il miglior esempio è il logger. Il thread del logger potrebbe essere un thread separato.

3) Compiti simili su dati diversi. Se esiste un'attività che si verifica su dati diversi ma di natura molto simile, thread diversi possono farlo. Il miglior esempio saranno le richieste del server.

E ovviamente molti altri come questo a seconda dell'applicazione.

* How do you reproduce error conditions and view what is happening as the application executes?

Utilizzo dei registri e delle stampe di debug nei registri. Prova a registrare anche l'id thread in modo da poter vedere cosa sta succedendo in ogni thread.
Un modo per generare una condizione di errore è inserire il ritardo deliberato (nel codice di debug) nei punti in cui si ritiene che si stia verificando il problema e arrestare forzatamente quel thread. Cose simili possono essere fatte anche nei debugger, ma non l'ho fatto finora.

* How do you visualize the interactions between the different concurrent parts of the application?

Inserisci i log nei tuoi lucchetti, in modo da sapere chi sta bloccando cosa e quando e chi ha provato a bloccarlo. Come ho detto prima, cerca di inserire l'ID thread nel registro per capire cosa sta succedendo in ogni thread.

Questo è solo il mio consiglio che è di circa 3 anni di lavoro sull'applicazione multithread e spero che sia di aiuto.


2
  • Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?

Vorrei prima chiedermi se l'applicazione (o il componente) vedrà effettivamente un beneficio dall'elaborazione simultanea, o in parole povere: dov'è il collo di bottiglia? La concorrenza ovviamente non fornirà sempre un vantaggio per l'investimento necessario per farla funzionare. Se sembra un candidato, allora lavorerei dal basso verso l'alto - cercando di trovare la più grande operazione o serie di operazioni che possono fare il suo lavoro efficacemente in isolamento - Non voglio far girare discussioni per insignificanti, inefficaci in termini di costi operazioni - cerco attori .

Lavorando con Erlang ho imparato ad amare assolutamente il concetto di utilizzo del passaggio di messaggi asincrono e il modello di attore per la concorrenza: è intuitivo, efficace e pulito.

Concorrenza dell'attore fuori dalla comprensione

Il modello dell'attore è costituito da alcuni principi chiave:

  • Nessuno stato condiviso
  • Processi leggeri
  • Passaggio di messaggi asincrono
  • Cassette postali per bufferizzare i messaggi in arrivo
  • Elaborazione delle cassette postali con corrispondenza dei motivi

Un attore è un processo che esegue una funzione. Qui un processo è un thread di spazio utente leggero (da non confondere con un tipico processo di sistema operativo pesante). Gli attori non condividono mai lo stato e quindi non devono mai competere per i blocchi per l'accesso ai dati condivisi. Invece, gli attori condividono i dati inviando messaggi immutabili. I dati immutabili non possono essere modificati, quindi le letture non richiedono un blocco.

Il modello di concorrenza Erlang è più semplice da comprendere e da eseguire per il debug rispetto al blocco e alla condivisione dei dati. Il modo in cui la tua logica è isolata rende facile eseguire test sui componenti passando loro i messaggi.

Lavorando con sistemi concorrenti questo è praticamente il modo in cui il mio progetto ha funzionato comunque in qualsiasi lingua - una coda da cui più thread trarrebbero dati, eseguire una semplice operazione e ripetere o reinserire nella coda. Erlang sta solo imponendo strutture di dati immutabili per prevenire effetti collaterali e ridurre i costi e la complessità della creazione di nuovi thread.

Questo modello non è esclusivo di Erlang, anche nel mondo Java e .NET esiste un modo per crearlo: darei un'occhiata a Concurrency and Coordination Runtime (CCR) e Relang (esiste anche Jetlang per Java).

  • Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Nella mia esperienza, l'unica cosa che posso aver fatto è impegnarmi a rintracciare / registrare tutto. Ogni processo / thread deve avere un identificatore e ogni nuova unità di lavoro deve avere un ID di correlazione. Devi essere in grado di guardare attraverso i tuoi registri e tracciare esattamente ciò che veniva elaborato e quando - non c'è magia che abbia visto per eliminarlo.

  • Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Vedi sopra, è brutto ma funziona. L'unica altra cosa che faccio è utilizzare i diagrammi di sequenza UML, ovviamente durante la fase di progettazione, ma è possibile utilizzarli per verificare che i componenti parlino nel modo desiderato.


1

- Le mie risposte sono specifiche per MS / Visual Studio -

Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?

Ci vorrà conoscenza del dominio, non ci sarà alcuna dichiarazione coperta qui per coprirlo.

Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Un sacco di registrazione, in grado di attivare / disattivare la registrazione in applicazioni di produzione al fine di catturarlo nella produzione. Intellitrace VS2010 dovrebbe essere in grado di aiutare con questo, ma non l'ho ancora usato.

Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Non ho una buona risposta a questa domanda, mi piacerebbe vederne una.


La registrazione cambierà il modo in cui il codice viene eseguito e quindi potrebbe portare all'errore che non viene visualizzato.
Matthew Leggi il

1

Non sono d'accordo con la tua affermazione che C non è progettato per la concorrenza. C è progettato per la programmazione di sistemi generali e gode di una tenacia nel sottolineare decisioni critiche da prendere, e continuerà a farlo per gli anni a venire. Questo è vero anche quando la decisione migliore potrebbe essere quella di non utilizzare C. Inoltre, la concorrenza in C è difficile tanto quanto la progettazione è complessa.

Cerco, al meglio delle mie capacità, di implementare i blocchi con l'idea che alla fine una programmazione veramente pratica senza blocchi potrebbe diventare una realtà per me. Per blocco, non intendo l'esclusione reciproca, intendo semplicemente un processo che implementa una concorrenza sicura senza la necessità di un arbitrato. Per pratica, intendo qualcosa che è più facile da trasportare di quanto non fosse da implementare. Ho anche pochissimo addestramento CS formale, ma suppongo che mi sia permesso di desiderare :)

In seguito, la maggior parte dei bug che incontro diventano relativamente superficiali, o così completamente sconvolgenti che mi ritiro in un pub. Il pub diventa un'opzione interessante solo quando la profilazione di un programma lo rallenta sufficientemente per esporre razze aggiuntive che non sono correlate a ciò che sto cercando di trovare.

Come altri hanno sottolineato, il problema che descrivi è estremamente specifico del dominio. Cerco solo, con la migliore delle mie capacità, di evitare ogni caso che possa richiedere un arbitrato (al di fuori del mio processo) ogni volta che sia possibile. Se sembra che possa essere un dolore regale, rivaluto la possibilità di dare a più thread o processi l'accesso simultaneo e non serializzato a qualcosa.

Poi di nuovo, lancia "distribuito" e l'arbitrato diventa un must. Hai un esempio specifico?


Per chiarire la mia affermazione, C non è stato progettato specificamente per e intorno alla concorrenza. Ciò è in contrasto con lingue come Go, Erlang e Scala che sono state progettate esplicitamente tenendo presente la concorrenza. Non intendevo dire che non puoi fare concorrenza con C.
Berin Loritsch il

1

Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Sulla base della mia esperienza, la risposta a questi due aspetti è la seguente:

Traccia distribuita

La traccia distribuita è una tecnologia che acquisisce i dati di temporizzazione per ogni singolo componente simultaneo del sistema e li presenta in formato grafico. Le rappresentazioni di esecuzioni simultanee sono sempre intercalate, consentendo di vedere cosa sta funzionando in parallelo e cosa no.

La traccia distribuita deve le sue origini a sistemi (ovviamente) distribuiti, che sono per definizione asincroni e altamente concorrenti. Un sistema distribuito con traccia distribuita consente alle persone di:

a) identificare importanti colli di bottiglia, b) ottenere una rappresentazione visiva delle "esecuzioni" ideali dell'applicazione e c) fornire visibilità su quale comportamento concorrente viene eseguito, d) ottenere dati temporali che possono essere utilizzati per valutare le differenze tra i cambiamenti nella propria sistema (estremamente importante se si dispone di SLA forti).

Le conseguenze della traccia distribuita sono tuttavia:

  1. Aggiunge un overhead a tutti i processi simultanei, poiché si traduce in più codice per l'esecuzione e l'invio potenzialmente su una rete. In alcuni casi, questo sovraccarico è molto significativo: anche Google utilizza il proprio sistema di traccia Dapper su un piccolo sottoinsieme di tutte le richieste per non rovinare l'esperienza dell'utente.

  2. Esistono molti strumenti diversi, non tutti interoperabili tra loro. Ciò è in qualche modo migliorato da standard come OpenTracing, ma non completamente risolto.

  3. Non ti dice nulla sulle risorse condivise e sul loro stato attuale. Potresti essere in grado di indovinare, in base al codice dell'applicazione e a ciò che ti mostra il grafico che vedi, ma non è uno strumento utile in questo senso.

  4. Gli strumenti attuali presuppongono che tu abbia memoria e spazio di archiviazione da risparmiare. L'hosting di un server di timeseries potrebbe non essere economico, a seconda dei vincoli.

Software di tracciamento errori

Mi collego a Sentry sopra principalmente perché è lo strumento più usato là fuori, e per una buona ragione - il software di tracciamento degli errori come Sentry dirotta l'esecuzione del runtime per inoltrare simultaneamente una traccia dello stack degli errori riscontrati su un server centrale.

Il vantaggio netto di tale software dedicato nel codice simultaneo:

  1. Gli errori duplicati non vengono duplicati . In altre parole, se uno o più sistemi simultanei riscontrano la stessa eccezione, Sentry incrementerà un rapporto sull'incidente, ma non invierà due copie dell'incidente.

Questo significa che puoi capire quale sistema simultaneo sta vivendo che tipo di errore senza dover passare attraverso innumerevoli segnalazioni di errori simultanee. Se hai mai subito spam e-mail da un sistema distribuito, sai come si sente l'inferno.

Puoi persino "taggare" diversi aspetti del tuo sistema concorrente (anche se questo presuppone che non hai un lavoro intercalato su esattamente un thread, che tecnicamente non è simultaneo poiché il thread sta semplicemente saltando tra le attività in modo efficiente ma deve comunque elaborare i gestori di eventi fino al completamento) e vedere una suddivisione degli errori per tag.

  1. È possibile modificare questo software di gestione degli errori per fornire ulteriori dettagli con le eccezioni di runtime. Quali risorse aperte ha avuto il processo? Esiste una risorsa condivisa che stava trattando questo processo? Quale utente ha riscontrato questo problema?

Questo, oltre alle meticolose tracce dello stack (e alle mappe di origine, se devi fornire una versione ridotta dei tuoi file), semplifica la determinazione di ciò che non va gran parte del tempo.

  1. (Specifico per Sentry) È possibile disporre di un dashboard di report Sentry separato per le esecuzioni di test del sistema, che consente di rilevare errori nei test.

Gli svantaggi di tale software includono:

  1. Come tutto, aggiungono alla rinfusa. Ad esempio, potresti non voler un tale sistema su hardware incorporato. Consiglio vivamente di eseguire una versione di prova di tale software, confrontando una semplice esecuzione con e senza il campionamento di alcune centinaia di esecuzioni su una macchina inattiva.

  2. Non tutte le lingue sono ugualmente supportate, poiché molti di questi sistemi si basano sulla cattura implicita di un'eccezione e non tutte le lingue presentano solide eccezioni. Detto questo, ci sono clienti per molti sistemi.

  3. Possono essere sollevati come un rischio per la sicurezza, dal momento che molti di questi sistemi sono essenzialmente di tipo chiuso. In tali casi, fai la tua dovuta diligenza nella ricerca o, se preferisci, fai il tuo.

  4. Potrebbero non fornirti sempre le informazioni di cui hai bisogno. Questo è un rischio con tutti i tentativi di aggiungere visibilità.

  5. La maggior parte di questi servizi sono stati progettati per applicazioni Web altamente concorrenti, quindi non tutti gli strumenti possono essere perfetti per il tuo caso d'uso.

In breve : avere visibilità è la parte più cruciale di qualsiasi sistema concorrente. I due metodi che descrivo sopra, insieme a dashboard dedicati su hardware e dati per ottenere un'immagine holidtic del sistema in un dato punto temporale, sono ampiamente utilizzati in tutto il settore proprio per affrontare tale aspetto.

Alcuni suggerimenti aggiuntivi

Ho trascorso più tempo di quanto mi occupi di riparare il codice da persone che hanno cercato di risolvere problemi simultanei in modi terribili. Ogni volta, ho trovato casi in cui le seguenti cose potrebbero migliorare notevolmente l'esperienza degli sviluppatori (che è altrettanto importante dell'esperienza dell'utente):

  • Affidati ai tipi . La digitazione esiste per convalidare il codice e può essere utilizzata in fase di esecuzione come protezione aggiuntiva. Laddove la digitazione non esiste, fare affidamento su asserzioni e un gestore degli errori adatto per rilevare gli errori. Il codice simultaneo richiede un codice difensivo e i tipi servono come il miglior tipo di convalida disponibile.

    • Verifica i collegamenti tra i componenti del codice , non solo il componente stesso. Non confonderlo con un test di integrazione completo, che verifica ogni collegamento tra ogni componente e anche in questo caso cerca solo una convalida globale dello stato finale. Questo è un modo terribile per rilevare errori.

Un buon test di collegamento verifica se, quando un componente comunica con un altro componente in modo isolato , il messaggio ricevuto e il messaggio inviato sono gli stessi che ci si aspetta. Se hai due o più componenti che fanno affidamento su un servizio condiviso per comunicare, girali tutti, invitali a scambiare messaggi tramite il servizio centrale e vedi se alla fine ottengono tutti ciò che ti aspetti.

Rompere i test che coinvolgono molti componenti in un test dei componenti stessi e un test su come comunicano anche ciascuno dei componenti ti dà maggiore fiducia nella validità del tuo codice. Avere una serie di test così rigorosi ti consente di far rispettare i contratti tra i servizi e di rilevare errori imprevisti che si verificano quando sono in esecuzione contemporaneamente.

  • Utilizzare gli algoritmi giusti per convalidare lo stato dell'applicazione. Sto parlando di cose semplici, come quando hai un processo principale in attesa che tutti i suoi lavoratori finiscano un compito e desideri passare al passaggio successivo solo se tutti i lavoratori sono completamente fatti, questo è un esempio di rilevamento globale terminazione, per la quale esistono metodologie note come l'algoritmo di Safra.

Alcuni di questi strumenti sono forniti in bundle con le lingue: Rust, ad esempio, garantisce che il tuo codice non avrà condizioni di gara in fase di compilazione, mentre Go presenta un rilevatore di deadlock integrato che gira anche in fase di compilazione. Se riesci a rilevare i problemi prima che colpiscano la produzione, è sempre una vittoria.

Una regola generale: progettare per guasti nei sistemi concorrenti . Anticipare che i servizi comuni andranno in crash o si rompano. Questo vale anche per il codice che non è distribuito tra macchine: il codice simultaneo su una singola macchina può fare affidamento su dipendenze esterne (come un file di registro condiviso, un server Redis, un maledetto server MySQL) che potrebbero scomparire o essere rimosse in qualsiasi momento .

Il modo migliore per farlo è convalidare di tanto in tanto lo stato dell'applicazione - fare controlli di integrità per ciascun servizio e assicurarsi che gli utenti di quel servizio vengano informati di cattiva salute. I moderni strumenti per container come Docker lo fanno abbastanza bene e dovrebbero essere utilizzati per sandbox.

Come si fa a capire cosa può essere reso simultaneo e cosa può essere reso sequenziale?

Uno dei più grandi lezioni che ho imparato lavorando su un sistema altamente concomitante è questo: non si può mai avere abbastanza metriche . Le metriche dovrebbero guidare assolutamente tutto nella tua applicazione - non sei un ingegnere se non stai misurando tutto.

Senza metriche, non puoi fare alcune cose molto importanti:

  1. Valuta la differenza apportata dalle modifiche al sistema. Se non sai se la manopola di sintonia A ha fatto aumentare la metrica B e la metrica C diminuisce, non sai come riparare il tuo sistema quando le persone spingono codice inaspettatamente maligno sul tuo sistema (e lo spingeranno sul tuo sistema) .

  2. Scopri cosa devi fare dopo per migliorare le cose. Fino a quando non sai che le applicazioni stanno esaurendo la memoria, non puoi discernere se dovresti ottenere più memoria o acquistare più disco per i tuoi server.

Le metriche sono così cruciali ed essenziali che ho fatto uno sforzo consapevole per pianificare ciò che voglio misurare prima ancora di pensare a ciò che un sistema richiederà. In effetti, le metriche sono così cruciali che credo che siano la risposta giusta a questa domanda: sai solo cosa può essere reso sequenziale o simultaneo quando misuri ciò che stanno facendo i bit nel tuo programma. La progettazione corretta utilizza numeri, non congetture.

Detto questo, ci sono sicuramente alcune regole pratiche:

  1. Sequenziale implica dipendenza. Due processi dovrebbero essere sequenziali se l'uno dipende dall'altro in qualche modo. I processi senza dipendenze dovrebbero essere simultanei. Tuttavia, pianificare un modo per gestire il fallimento del flusso che non impedisce ai processi a valle di attendere indefinitamente.

  2. Non mescolare mai un'attività associata I / O con un'attività associata alla CPU sullo stesso core. Non (ad esempio) non scrivere un crawler Web che avvia dieci richieste simultanee nello stesso thread, non le raschia non appena arrivano e non si prevede di ridimensionarle a cinquecento: le richieste I / O vanno in coda in parallelo, ma la CPU li attraverserà comunque in serie. (Questo modello basato su eventi a thread singolo è popolare, ma è limitato a causa di questo aspetto - piuttosto che capirlo, le persone semplicemente si danno la mano e dicono che Nodo non si ridimensiona, per darvi un esempio).

Un singolo thread può eseguire molte operazioni di I / O. Ma per sfruttare appieno la concorrenza del tuo hardware, usa threadpools che insieme occupano tutti i core. Nell'esempio sopra, l'avvio di cinque processi Python (ognuno dei quali può utilizzare un core su una macchina a sei core) solo per il lavoro con la CPU e un sesto thread Python solo per il lavoro di I / O si ridimensioneranno molto più velocemente di quanto si pensi.

L'unico modo per sfruttare la concorrenza della CPU è attraverso un threadpool dedicato. Un singolo thread è spesso abbastanza buono per un sacco di lavoro di I / O associato. Questo è il motivo per cui i server Web basati su eventi come Nginx si adattano meglio (fanno semplicemente il lavoro di I / O associato) di Apache (che unisce il lavoro di I / O associato a qualcosa che richiede CPU e avvia un processo per richiesta), ma perché usare Node per eseguire decine di migliaia di calcoli GPU ricevuti in parallelo è un'idea terribile .


0

Bene, per il processo di verifica, quando si progetta un grande sistema concorrente, tendo a testare il modello usando LTSA - Labeled Transition System Analyzer . È stato sviluppato dal mio vecchio tutor, che è una specie di veterano nel campo della concorrenza ed è attualmente responsabile del reparto di informatica presso Imperial.

Per quanto riguarda l'elaborazione di ciò che può e non può essere simultaneo, ci sono analizzatori statici che potrebbero dimostrarlo, credo, anche se tendo solo a disegnare diagrammi di pianificazione per sezioni critiche, lo stesso che faresti per la gestione del progetto. Quindi identificare le sezioni che eseguono ripetutamente la stessa operazione. Un percorso rapido è solo quello di trovare i loop, in quanto tendono ad essere le aree che beneficiano dell'elaborazione parallela.


0

Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?

Praticamente ogni cosa che scrivi può trarre vantaggio dalla concorrenza, in particolare il caso d'uso "Dividi una conquista". Una domanda molto migliore è cosa dovrebbe essere concorrente?

Threading di Joseph Albahari in C # elenca cinque usi comuni.

Il multithreading ha molti usi; ecco i più comuni:

Mantenimento di un'interfaccia utente reattiva

Eseguendo attività che richiedono molto tempo su un thread "worker" parallelo, il thread dell'interfaccia utente principale è libero di continuare l'elaborazione degli eventi di tastiera e mouse.

Fare un uso efficiente di una CPU altrimenti bloccata

Il multithreading è utile quando un thread è in attesa di una risposta da un altro computer o hardware. Mentre un thread è bloccato durante l'esecuzione dell'attività, altri thread possono trarre vantaggio dal computer altrimenti privo di problemi.

Programmazione parallela

Il codice che esegue calcoli intensivi può essere eseguito più rapidamente su computer multicore o multiprocessore se il carico di lavoro è condiviso tra più thread in una strategia di "divisione e conquista" (vedere Parte 5).

Esecuzione speculativa

Su macchine multicore, a volte è possibile migliorare le prestazioni predicendo qualcosa che potrebbe essere necessario fare e quindi farlo in anticipo. LINQPad utilizza questa tecnica per accelerare la creazione di nuove query. Una variante consiste nell'eseguire in parallelo diversi algoritmi che risolvono tutti lo stesso compito. Qualunque sia il primo che finisce per primo "vince", questo è efficace quando non si può sapere in anticipo quale algoritmo verrà eseguito più velocemente.

Consentire l'elaborazione simultanea delle richieste

Su un server, le richieste client possono arrivare contemporaneamente e quindi devono essere gestite in parallelo (.NET Framework crea automaticamente i thread per questo se si utilizzano ASP.NET, WCF, Web Services o Remoting). Ciò può essere utile anche su un client (ad es. Gestione di reti peer-to-peer o anche richieste multiple da parte dell'utente).

Se non stai provando a fare una delle cose sopra, probabilmente faresti meglio a pensarci bene.

Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Se si utilizza .NET e sono stati scritti casi d'uso, è possibile utilizzare CHESS in grado di ricreare condizioni di interlacciamento di thread specifiche che consentono di testare la correzione.

Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Dipende dal contesto. Per gli scenari dei lavoratori penso a un manager subordinato. Manger dice al subordinato di fare qualcosa e attende aggiornamenti di stato.

Per compiti non correlati simultanei, penso a ascensori o automobili in corsie separate del traffico.

Per la sincronizzazione a volte penso ai semafori o agli stili di svolta.

Inoltre, se stai usando C # 4.0, potresti dare un'occhiata alla Task Parallel Library


0

La mia risposta a queste domande sono:

  • Come si fa a capire cosa può essere reso simultaneo rispetto a ciò che deve essere sequenziale?

Per prima cosa devo sapere perché dovrei usare la concorrenza, perché ho scoperto che le persone vengono abbandonate con l'idea alla base della concorrenza, ma non sempre penso al problema che stanno cercando di risolvere.

Se devi simulare una situazione di vita reale come code, flussi di lavoro, ecc., Molto probabilmente dovrai utilizzare un approccio simultaneo.

Ora che so che dovrei usarlo, è tempo di analizzare il trade off, se hai molti progressi, potresti pensare al sovraccarico di comunicazione, ma se devi nuovo, potresti finire senza una soluzione concorrente (rianalizzare il problema se così.)

  • Come si riproducono le condizioni di errore e si visualizza cosa sta succedendo mentre l'applicazione viene eseguita?

Non sono un esperto in materia, ma penso che per i sistemi concorrenti questo non sia l'approccio corretto. Dovrebbe essere scelto un approccio teorico, cercando i 4 requisiti di deadlock in aree critiche:

  1. Non preventività
  2. Aspetta e aspetta
  3. Esclusione motoria
  4. Catena circolare

    • Come si visualizzano le interazioni tra le diverse parti simultanee dell'applicazione?

Cerco innanzitutto di identificare chi sono i partecipanti alle interazioni, quindi come comunicano e con chi. Infine, grafici e diagrammi di interazione mi aiutano a visualizzare. La mia buona vecchia lavagna non può essere battuta da nessun altro tipo di supporto.


0

Sarò schietto. Adoro gli strumenti. Uso molti strumenti. Il mio primo passo è tracciare i percorsi previsti per il flusso di stato. Il mio prossimo passo è cercare di capire se ne valga la pena o se il flusso di informazioni richiesto renderà il codice seriale troppo spesso. Quindi, proverò a disegnare alcuni modelli semplici. Questi possono variare da una pila di sculture di stuzzicadenti grezzi ad alcuni semplici esempi simili in pitone. Successivamente, guardo un paio dei miei libri preferiti, come il piccolo libro di semafori, e vedo se qualcuno ha già trovato una soluzione migliore al mio problema.

Quindi inizio a scrivere codice.
Stavo solo scherzando. Prima un po 'più di ricerca. Mi piace sedermi con un altro hacker e seguire un'esecuzione prevista del programma ad alto livello. Se sorgono domande, passiamo a un livello inferiore. È importante scoprire se qualcun altro è in grado di comprendere la tua soluzione abbastanza bene da mantenerla.

Finalmente inizio a scrivere codice. Prima provo a renderlo molto semplice. Solo il percorso del codice, niente di speciale. Sposta il minor stato possibile. Evita le scritture. Evita letture che potrebbero essere in conflitto con le scritture. Evita soprattutto le scritture che potrebbero essere in conflitto con le scritture. È molto facile scoprire che hai un numero positivamente tossico di questi e che la tua bella soluzione è improvvisamente poco più di un approccio seriale che distrugge la cache.

Una buona regola è quella di utilizzare i framework ovunque tu sia. Se stai scrivendo tu stesso i componenti di threading di base, come le buone strutture di dati sincronizzate o i sincro-primitivi effettivi di dio-proibiti, quasi sicuramente ti lascerai a bocca aperta.

Finalmente strumenti. Il debug è molto difficile. Uso valgrind \ callgrind su Linux in combinazione con PIN e studi paralleli su Windows. Non provare a eseguire il debug di questa roba a mano. Probabilmente puoi. Ma probabilmente vorrai non averlo fatto. Dieci ore a padroneggiare alcuni strumenti potenti e alcuni buoni modelli ti faranno risparmiare centinaia di ore dopo.

Soprattutto, lavora in modo incrementale. Lavora con cura. Non scrivere codice simultaneo quando sei stanco. Non scriverlo mentre sei affamato. In effetti, se puoi evitarlo, semplicemente non scriverlo. La concorrenza è difficile e ho scoperto che molte app che la elencano come funzionalità spesso vengono fornite con essa come unica funzionalità.

In sintesi:
Inizia:
Think
Talk
Test
Scrivi semplicemente
Leggi
test
Scrivi
debug
GOTO Inizia

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.