Come spiegare perché il multi-threading è difficile

83

Sono un programmatore abbastanza bravo, il mio capo è anche un buon programmatore. Anche se sembra sottovalutare alcune attività come il multi-threading e quanto possa essere difficile (trovo molto difficile fare qualcosa di più che eseguire alcuni thread, in attesa che tutti finiscano, quindi restituire i risultati).

Nel momento in cui inizi a preoccuparti di situazioni di stallo e condizioni di stallo, lo trovo molto difficile, ma il capo non sembra apprezzarlo - non penso che abbia mai incontrato questo. Basta schiaffeggiarlo è praticamente l'atteggiamento.

Quindi, come posso presentarlo, o spiegare perché potrebbe sottovalutare la complessità della concorrenza, il parallelismo e il multi-threading? O forse mi sbaglio?

Modifica: solo un po 'su ciò che ha fatto: scorrere un elenco, per ogni elemento in quell'elenco creare un thread che esegue un comando di aggiornamento del database in base alle informazioni contenute in quell'elemento. Non sono sicuro di come ha controllato quanti thread eseguiti contemporaneamente, suppongo che li abbia aggiunti a una coda se ce ne fossero stati troppi in esecuzione (non avrebbe usato un semaforo).

    
posta Mr Shoubs 02.06.2011 - 11:18
fonte

10 risposte

29
  1. Se puoi contare su qualsiasi esperienza matematica, illustra come un normale flusso di esecuzione che è essenzialmente deterministico diventa non solo non deterministico con diversi thread, ma esponenzialmente complesso, perché devi assicurarti ogni possibile interlacciamento delle istruzioni della macchina farà comunque la cosa giusta. Un semplice esempio di un aggiornamento perduto o di una situazione di lettura sporca spesso apre gli occhi.

  2. "Slap un blocco su di esso" è la soluzione banale ... risolve tutti i tuoi problemi se non ti preoccupi delle prestazioni. Prova ad illustrare la quantità di prestazioni che avrebbe colpito se, ad esempio, Amazon dovesse bloccare l'intera costa est ogni volta che qualcuno ad Atlanta ordina un libro!

risposta data 02.06.2011 - 11:32
fonte
79

Il multi-threading è semplice. La codifica di un'applicazione per multi-threading è molto, molto semplice.

C'è un trucco semplice, e questo è usare una coda di messaggi ben progettata (fai non fai il tuo) per passare i dati tra i thread.

La parte difficile è cercare di avere più thread per aggiornare magicamente un oggetto condiviso in qualche modo. Ecco quando diventa soggetto a errori perché la gente non presta attenzione alle condizioni di gara che sono presenti.

Molte persone non utilizzano code di messaggi e tentano di aggiornare oggetti condivisi e creare problemi per se stessi.

Ciò che diventa difficile è la progettazione di un algoritmo che funzioni bene quando si passano dati tra più code. È difficile. Ma la meccanica dei thread coesistenti (tramite code condivise) è facile.

Inoltre, nota che le discussioni condividi risorse I / O. È improbabile che un programma di I / O associato (cioè connessioni di rete, operazioni sui file o operazioni di database) sia più veloce con molti thread.

Se vuoi illustrare il problema dell'aggiornamento dell'oggetto condiviso, è semplice. Siediti sul tavolo con un mazzo di carte di carta. Scrivi una semplice serie di calcoli - 4 o 6 formule semplici - con molto spazio nella pagina.

Ecco il gioco. Ognuno di voi legge una formula, scrive una risposta e inserisce una carta con la risposta.

Ognuno di voi farà metà del lavoro, giusto? Hai finito in metà tempo, vero?

Se il tuo capo non pensa molto e inizia solo, in qualche modo finirai per entrare in conflitto e entrambi scrivono le risposte alla stessa formula. Questo non ha funzionato perché c'è una condizione di razza intrinseca tra voi due che leggete prima di scrivere. Niente ti impedisce di leggere la stessa formula e di sovrascrivere le rispettive risposte.

Ci sono molti, molti modi per creare condizioni di gara con risorse male o non bloccate.

Se vuoi evitare tutti i conflitti, tagli la carta in una pila di formule. Ne togli una dalla coda, scrivi la risposta e posta le risposte. Nessun conflitto perché entrambi leggete da una coda di messaggi di sola lettura.

    
risposta data 02.06.2011 - 12:17
fonte
25

La programmazione multi-thread è probabilmente la soluzione più difficile alla concorrenza. Fondamentalmente è un'astrazione piuttosto bassa di ciò che la macchina effettivamente fa.

Esistono numerosi approcci, come il modello di attore o (software) memoria transazionale , che sono molto più semplici. O lavorando con strutture dati immutabili (come liste e alberi).

In generale, una corretta separazione dei problemi rende più facile il multithreading. Qualcosa, tutto da dimenticare spesso, quando la gente genera 20 thread, tutti tentano di elaborare lo stesso buffer. Utilizzare reattori in cui è necessaria la sincronizzazione e in genere passare i dati tra diversi lavoratori con code di messaggi.
Se hai un blocco nella logica dell'applicazione, hai fatto qualcosa di sbagliato.

Quindi sì, tecnicamente, il multi-threading è difficile.
"Slap a lock on it" è la soluzione meno scalabile ai problemi di concorrenza, e in realtà sconfigge l'intero scopo del multi-threading. Quello che fa è ripristinare un problema in un modello di esecuzione non simultaneo. Più lo fai, più è probabile che tu abbia solo un thread in esecuzione al momento (o 0 in un deadlock). Sconfigge l'intero scopo.
È come dire "Risolvere i problemi del terzo mondo è facile, basta lanciare una bomba su di esso". Solo perché c'è una soluzione banale, questo non rende banale il problema, dal momento che ti interessa la qualità del risultato.

Ma in pratica, risolvere questi problemi è altrettanto difficile di qualsiasi altro problema di programmazione ed è meglio farlo con le astrazioni appropriate. Il che lo rende abbastanza facile in effetti.

    
risposta data 02.06.2011 - 12:21
fonte
14

Penso che ci sia un angolo non tecnico di questa domanda: IMO è un problema di fiducia. Di solito ci viene chiesto di riprodurre app complesse come - oh, non so - Facebook per esempio. Sono giunto alla conclusione che se devi spiegare la complessità di un compito al non iniziato / alla gestione - allora qualcosa è marcio in Danimarca.

Anche se altri programmatori ninja potrebbero eseguire l'operazione in 5 minuti, le tue stime si basano sulle tue capacità personali. Il tuo interlocutore dovrebbe imparare a fidarsi della tua opinione sull'argomento o assumere qualcuno di cui accetta la parola.

La sfida non consiste nel riferire le implicazioni tecniche, che le persone tendono a ignorare o non riescono a cogliere attraverso la conversazione, ma a stabilire una relazione di reciproco rispetto.

    
risposta data 02.06.2011 - 13:03
fonte
6

Un semplice esperimento di pensiero per comprendere i deadlock è il problema " dining philosopher ". Uno degli esempi che tendo ad usare per descrivere quanto possono essere cattive condizioni di gara è la Therac 25 situazione.

"Basta schivare un blocco su di esso" è la mentalità di qualcuno che non ha incontrato bug difficili con il multi-threading. Ed è possibile che lui pensi di esagerare con la gravità della situazione (non lo faccio - è possibile far saltare in aria roba o uccidere persone con bug di condizioni di razza, specialmente con software embedded che finisce in auto).

    
risposta data 02.06.2011 - 16:32
fonte
3

Le applicazioni concorrenti non sono deterministiche. Con la quantità eccezionalmente piccola di codice globale che il programmatore ha riconosciuto come vulnerabile, non si controlla quando una parte di un thread / processo viene eseguita in relazione a qualsiasi parte di un altro thread. Il test è più difficile, richiede più tempo ed è improbabile che trovi tutti i difetti relativi alla concorrenza. Difetti, se trovati, sono così sottili che non possono essere riprodotti in modo coerente, quindi il fissaggio è difficile.

Quindi l'unica applicazione simultanea corretta è quella che è provabilmente corretta, qualcosa che non è spesso praticata nello sviluppo del software. Di conseguenza, la risposta di S.Lot è il miglior consiglio generale, poiché il passaggio dei messaggi è relativamente facile da dimostrare.

    
risposta data 02.06.2011 - 23:07
fonte
3

Risposta breve in due parole: NONDETERMINISMO OSSERVABILE

Risposta lunga: Dipende dall'approccio alla programmazione concorrente che usi dato il tuo problema. Nel libro Concetti, tecniche e modelli di programmazione per computer , gli autori spiegano chiaramente quattro principali approcci pratici per scrivere programmi concorrenti:

  • Programmazione sequenziale : un approccio di base che non ha concorrenza;
  • Concorrenza dichiarativa : utilizzabile quando non vi è alcun nondeterminismo osservabile;
  • Concorrenza di passaggio dei messaggi : passaggio di messaggi simultanei tra molte entità, in cui ciascuna entità elabora internamente il messaggio in modo sequenziale;
  • Concorrenza di stato condivisa : thread che aggiorna oggetti passivi condivisi mediante azioni atomiche a grana grossa, ad es. serrature, monitor e transazioni;

Ora il più semplice di questi quattro approcci, a parte l'evidente programmazione sequenziale, è concorrenza dichiarativa , perché i programmi scritti utilizzando questo approccio hanno nessun nondeterminismo osservabile . In altre parole, ci sono condizioni di gara , poiché la condizione di gara è solo un comportamento non deterministico osservabile.

Ma la mancanza di nondeterminismo osservabile significa che là ci sono alcuni problemi che non possiamo affrontare usando la concorrenza dichiarativa. Qui è dove entrano in gioco gli ultimi due approcci non così facili. La parte non così facile è una conseguenza del nondeterminismo osservabile. Ora entrambi cadono sotto il modello concomitante di stato e sono anche equivalenti in espressività. Ma a causa del sempre crescente numero di core per CPU, sembra che l'industria abbia preso più interesse recentemente nella concorrenza di passaggio di messaggi, come si può vedere nell'ascesa delle librerie di passaggio dei messaggi (ad esempio Akka per JVM) o linguaggi di programmazione (ad es. Erlang ).

La già citata libreria Akka, che è supportata da un modello teorico di attore, semplifica la creazione di applicazioni concorrenti, poiché non devi più occuparti di lock, monitor o transazioni. D'altra parte, richiede un approccio diverso alla progettazione di soluzioni, cioè pensare in un modo di attori gerarchicamente compositi. Si potrebbe dire che richiede una mentalità totalmente diversa, che ancora una volta può essere ancora più difficile rispetto all'utilizzo della concorrenza condivisa di stato semplice.

La programmazione concorrente è difficile a causa del nondeterminismo osservabile, ma quando si utilizza l'approccio giusto per il problema specifico e la libreria corretta che supporta tale approccio, è possibile evitare un sacco di problemi.

    
risposta data 23.08.2015 - 11:38
fonte
0

Mi è stato inizialmente insegnato che poteva sollevare problemi vedendo un semplice programma che avviava 2 thread e li faceva stampare entrambi sulla console contemporaneamente da 1-100. Invece di:

1
1
2
2
3
3
...

Ottieni qualcosa di più simile a questo:

1
2
1
3
2
3
...

Eseguilo di nuovo e potresti ottenere risultati completamente diversi.

Molti di noi sono stati addestrati ad assumere che il nostro codice verrà eseguito in sequenza. Con la maggior parte del multi-threading non possiamo dare per scontato "out of the box".

    
risposta data 02.06.2011 - 22:15
fonte
-3

Prova a usare diversi martelli per schiacciare in un mazzetto di chiodi ravvicinati senza una certa comunicazione tra quelli che tengono i martelli ... (supponi che siano bendati).

Incrementa questo per costruire una casa.

Adesso stai dormendo di notte per imminirti, sei l'architetto. :)

    
risposta data 02.06.2011 - 21:10
fonte
-3

Parte facile: usa il multithreading con le funzionalità contemporanee di framework, sistemi operativi e hardware, come semafori, code, contatori interbloccati, tipi di scatole atomiche ecc.

Parte difficile: implementare le funzionalità stesse senza utilizzare funzionalità al primo posto, potrebbe essere fatta eccezione per alcune funzionalità hardware molto limitate, basandosi solo sulle garanzie di coerenza clocking su più core.

    
risposta data 02.06.2011 - 21:16
fonte

Leggi altre domande sui tag