2009-07-14 12 views
5

Man mano che i computer multiprocessore e multi-core diventano sempre più onnipresenti, si sta semplicemente sparando un nuovo thread un (relativamente) semplice e indolore modo di semplificare il codice? Ad esempio, in un progetto personale corrente, ho un server di rete in ascolto su una porta. Poiché questo è solo un progetto personale, è solo un'app desktop, con una GUI integrata per la configurazione. Quindi, l'applicazione si legge qualcosa di simile:Spara un thread una risposta valida per semplificare il codice?

 
Main() 
    Read configuration 
    Start listener thread 
    Run GUI 

Listener Thread 
    While the app is running 
     Wait for a new connection 
     Run a client thread for the new connection 

Client Thread 
    Write synchronously 
    Read synchronously 
    ad inifinitum, or till they disconnect 

Questo approccio significa che, mentre io devo preoccupare un sacco di blocco, con i potenziali problemi che comporta, io evitare un sacco di spaghetti code dalle chiamate assynchronous, ecc

Una versione leggermente più insidiosa di questo è venuto oggi quando stavo lavorando sul codice di avvio. L'avvio è stato rapido, ma utilizzava il caricamento lento per un sacco della configurazione, il che significava che mentre l'avvio era veloce, la connessione e l'utilizzo del servizio era difficile a causa del ritardo mentre caricava diverse sezioni (in realtà era misurabile in tempo reale tempo, fino a 3-10 secondi a volte). Così mi sono spostato su una strategia diversa, all'avvio, passando in rassegna tutto e costringendo il pigro carico a dare il via ... ma ciò ha fatto sì che iniziasse a rallentare in modo proibitivo; alzati, vai a prendere un caffè lentamente. Soluzione finale: gettare il ciclo in un filo separato con feedback nel vassoio di sistema mentre è ancora in fase di caricamento.

È questo atteggiamento "Meh, buttalo in un altro thread, andrà tutto bene" ok? A che punto inizi a ottenere rendimenti decrescenti e/o prestazioni ridotte?

+1

Bella domanda e una buona aggiunta alla knowledge base SO. – Alan

+0

Sono d'accordo, davvero una bella domanda. –

risposta

19

Il multithreading fa un sacco di cose, ma non credo che la "semplificazione" sia mai stata una di queste.

+1

Se devi fare due o più cose contemporaneamente, l'unica alternativa sarebbe quella di intrappolarle in qualche modo nel codice sequenziale. Quindi in questo senso il multithreading semplifica il codice, sebbene non sia ancora semplice. – starblue

+1

Qual è il mio argomento per thread + chiamate sincrone per ie. implementando un protocollo di rete sincrono. Sì, i blocchi e la sincronizzazione non sono argomenti semplici ... Penso che la parola che stavo cercando quando ho scritto questa domanda era probabilmente "consolidante" o qualcosa di simile. –

7

È un ottimo modo per introdurre bug nel codice.

L'utilizzo di più thread correttamente non è facile. Non dovrebbe essere tentato dai nuovi sviluppatori.

+0

Perché il downvote. Avete bisogno di sincronizzazione spiegata, deadlock? –

+0

Per la cronaca, non sono stato io. E personalmente, non mi considero un nuovo sviluppatore, anche se questo potrebbe essere l'orgoglio personale. Capisco i deadlock e la sincronizzazione, se non sempre gli strumenti utilizzati per raggiungerli/evitarli. –

+1

Non stavo parlando di te come nuovo sviluppatore. La tua domanda sembrava riguardare l'onnipresente processo "aggiungi nuovo thread", il che significa che sarebbe stato utilizzato anche dai nuovi sviluppatori. –

4

A mio parere, la programmazione multi-thread è piuttosto in alto sulla scala di difficoltà (e complessità), insieme alla gestione della memoria. Per me, il "Meh, lo butto in un altro thread, andrà bene" l'atteggiamento è un po 'troppo casual. Pensa a lungo e duramente devi, prima di forare le discussioni che fai.

2

In questo modo è possibile eseguire il debug delle condizioni di gara e gestire i blocchi e i problemi di sottrazione.

Non lo userei a meno che non ci fosse un bisogno reale.

+0

Bloccate più del necessario, poi allontanateli quando causano problemi di prestazioni dopo aver guardato bene come è possibile? –

0

Penso che non abbiate altra scelta che trattare i thread soprattutto con connessioni in rete e simultanee. I thread rendono il codice più semplice? Io non la penso così Ma senza di loro come si programma un server in grado di gestire più di 1 client contemporaneamente?

+0

Sì, ma non li aggiungi per rendere il codice più semplice. Li aggiungi quando li hai _ e poi lo fai con attenzione. –

+0

Inoltre, utilizzando le chiamate puramente asincrone è possibile ottenere questo, almeno per quanto riguarda il proprio codice. Naturalmente, queste chiamate utilizzano ancora thread separati dietro le quinte ... –

+3

E hai ancora problemi di sincronizzazione. Inoltre, saresti sorpreso, ma molte persone non ottengono realmente il codice asincrono. –

4

No.

semplicità e chiarezza, multithreading aumenta la complessità ed è un modo quasi banale per aggiungere errori al codice. Ci sono problemi concurrency come synchronization, deadlock, race conditions e priority inversion per citarne alcuni.

In secondo luogo, i guadagni in termini di prestazioni non sono automatici. Recentemente, in MSDN Magazine c'è stato un eccellente article lungo queste linee. I dettagli salienti sono che una determinata operazione richiedeva 46 secondi per dieci iterazioni codificate come un'operazione a thread singolo. L'autore ha parallelizzato l'operazione in modo ingenuo (un thread per quattro core) e l'operazione è scesa a 30 secondi per dieci iterazioni.Sembra ottimo fino a quando non si tiene conto del fatto che l'operazione ora consuma il 300% in più di potenza di elaborazione, ma ha registrato solo un aumento dell'efficienza del 34%. Non vale la pena consumare tutta la potenza di elaborazione disponibile per un guadagno del genere.

+0

Ti darei un altro +1 se potessi per il link sull'inversione prioritaria ... non ho mai sentito parlare di quella particolare prima. –

2

Leggere su Amdahl's law, meglio riassunto da "La velocità di un programma che utilizza più processori nel calcolo parallelo è limitata dal tempo necessario per la frazione sequenziale del programma."

Come risulta, se solo una piccola parte della tua app può essere eseguita in parallelo non otterrai molti guadagni, ma potenzialmente molti bug difficili da debugare.

+0

Ulteriori informazioni interessanti seguendo alcuni link: http://www.cilk.com/multicore-blog/bid/5365/What-the-is-Parallelism-Anyhow –

1

Non intendo essere capovolto, ma cosa c'è nel file di configurazione che impiega così tanto tempo a caricarsi? Questa è l'origine del tuo problema, giusto?

Prima di spawnare un altro thread per gestirlo, è possibile che si riduca? Ridotto, forse messo in un altro formato di dati che sarebbe più veloce, ecc?

Quanto spesso cambia? È qualcosa che è possibile analizzare una volta all'inizio della giornata e inserire le variabili nella memoria condivisa in modo che le esecuzioni successive del programma principale possano semplicemente allegare e ottenere i valori necessari da lì?

+0

Compilare una serie di brevi script C#. Sì, è possibile memorizzarli nella cache tra le esecuzioni e, a lungo termine, probabilmente lo farò. Ma per il gusto della discussione e del mio cervello, al momento non lo sono. Al momento non hanno alcun dato univoco garantito ad essi collegato per collegarli al resto dei dati ad essi associati. Quanto a quanto spesso cambiano, di solito almeno una volta per corsa. –

+0

Due pensieri quindi. (1) Probabilmente vale la pena aggiungere la complessità dei thread aggiuntivi. (2) Si potrebbe anche iniziare su alcuni schemi di cache ora. Mentre potrebbero esserci delle sovrapposizioni tra le soluzioni a breve e lungo termine, probabilmente finirai per fare la maggior parte del lavoro due volte. – Duck

+0

Per quello che vale, ho finito per gestire ThreadPool per ciascuna delle compilation, quindi ora l'intera configurazione viene caricata in pochi secondi anziché in un minuto. Probabilmente il caching sarà ancora all'ordine del giorno ad un certo punto ... ma non è più un problema di "necessità di fare". –

1

Mentre concordo con chiunque altro nel dire che il multithreading non semplifica il codice, può essere utilizzato per semplificare notevolmente l'esperienza utente dell'applicazione.

Considerate un'applicazione che ha molti widget interattivi (ne sto sviluppando uno in questo momento) - nel flusso di lavoro della mia applicazione, un utente può "costruire" il progetto corrente su cui stanno lavorando. Ciò richiede la disabilitazione dei widget interattivi che la mia applicazione presenta all'utente e che presenta una finestra di dialogo con una barra di avanzamento indeterminata e un messaggio amichevole "please wait".

La "build" si verifica su un thread in background; se dovesse accadere sul thread dell'interfaccia utente renderebbe l'esperienza utente meno piacevole - dopo tutto, non è divertente non essere in grado di dire se è possibile fare clic su un widget in un'applicazione mentre è in esecuzione un'attività in background (tosse, Visual Studio). Per non dire che VS non usa thread in background, sto solo dicendo che la loro esperienza utente potrebbe migliorare. Ma sto divagando.

L'unica cosa di cui mi prendo in considerazione nel titolo del post è che si pensa di spegnere i thread quando è necessario eseguire attività, in genere preferisco riutilizzare i thread. In .NET, generalmente preferisco usare il thread di sistema creare una nuova discussione ogni volta che voglio fare qualcosa, per il bene delle prestazioni.

+0

Utilizzando il pool di thread o altro, si sta ancora creando un nuovo thread, quindi non vedo davvero la differenza, a parte il fatto che .NET ne sta gestendo la creazione e l'esecuzione, quando e come ritiene opportuno. –

+1

No - per MSDN: "Esiste un solo oggetto ThreadPool per processo Il pool di thread viene creato la prima volta che si chiama ThreadPool.QueueUserWorkItem, o quando un timer o un'operazione di attesa registrata mette in coda un metodo di callback" - c'è solo un costo in termini di tempo nella creazione del pool di thread. Dopodiché, il pool di thread viene riscaldato e rimane in vita fino alla fine del processo: semplicemente si inoltrano lavori asincroni e si prende cura di tutto il resto, anziché creare un thread (un sacco di overhead) ogni volta che si desidera eseguire qualcosa. –

+0

Penso che intendesse la creazione e l'esecuzione del thread, non del pool. –

1

Ho intenzione di fornire un certo equilibrio contro il "no" unanime.

DISCLAIMER: Sì, i thread sono complicati e possono causare un sacco di problemi. Tutti gli altri hanno sottolineato questo.

Per esperienza, una sequenza di blocco di letture/scritture su una presa (che richiede un cavo separato) è molto più semplice da rispetto a quelle non bloccanti. Con il blocco delle chiamate, è possibile conoscere lo stato della connessione semplicemente osservando il punto in cui ci si trova nella funzione. Con le chiamate non bloccanti, è necessario un gruppo di variabili per registrare lo stato della connessione e controllarle e modificarle ogni volta che si interagisce con la connessione. Con il blocco delle chiamate, puoi semplicemente dire "leggi i prossimi X byte" o "leggi fino a trovare X" e lo farà effettivamente (o fallirà). Con le chiamate non bloccanti, devi gestire i dati frammentati che di solito richiedono il mantenimento di buffer temporanei e il loro riempimento, se necessario. Finisci anche tu a controllare se hai ricevuto abbastanza dati ogni volta che ricevi poco più. Inoltre devi tenere un elenco di connessioni aperte e gestire chiusure impreviste per tutte loro.

Non ottiene molto più semplice di questo:

void WorkerThreadMain(Connection connection) { 
    Request request = ReadRequest(connection); 
    if(!request) return; 
    Reply reply = ProcessRequest(request); 
    if(!connection.isOpen) return; 
    SendReply(reply, connection); 
    connection.close(); 
} 

vorrei notare che questo "ascoltatore genera fuori un thread di lavoro per ogni connessione" modello è come i server web sono stati progettati, e presumo è come vengono progettate molte richieste/risposte di applicazioni server.

Quindi, in conclusione, ho riscontrato il codice spaghetti asincrono del socket che hai menzionato e la generazione dei thread di lavoro per ogni connessione è stata una buona soluzione. Detto questo, tirare le discussioni a un problema dovrebbe essere di solito l'ultima risorsa.

Problemi correlati