2010-05-05 14 views
12

Ho esaminato la praticità di alcune delle nuove funzionalità parallele in .Net 4.0.Funzionalità parallele in .Net 4.0

Dire che ho il codice in questo modo:

foreach (var item in myEnumerable) 
    myDatabase.Insert(item.ConvertToDatabase()); 

Immaginate myDatabase.Insert sta eseguendo alcuni lavori di inserire in un database SQL.

Teoricamente si potrebbe scrivere:

Parallel.ForEach(myEnumerable, item => myDatabase.Insert(item.ConvertToDatabase())); 

E automaticamente si ottiene il codice che sfrutta più core.

Ma cosa succede se myEnumerable può essere interagito solo con un singolo thread? La classe Parallel sarà enumerata da un singolo thread e invierà il risultato solo ai thread worker nel loop?

E se myDatabase può essere interagito solo con un singolo thread? Non sarebbe certamente meglio creare una connessione al database per iterazione del ciclo.

Infine, cosa succede se il mio "oggetto var" sembra essere un UserControl o qualcosa che deve essere interagito con il thread dell'interfaccia utente?

Quale modello di progettazione dovrei seguire per risolvere questi problemi?

Mi sembra che passare a Parallel/PLinq/etc non sia esattamente facile quando si ha a che fare con applicazioni reali.

risposta

12

L'interfaccia IEnumerable<T> non è intrinsecamente thread-safe. Parallel.ForEach gestirà automaticamente questo e solo parallelizzare gli elementi che escono dall'enumerazione. (La sequenza sarà sempre attraversata, un elemento alla volta, in ordine - ma gli oggetti risultanti verranno parallelizzati.)

Se le tue classi (es .: la T) non possono essere gestite da più thread, allora non dovresti provare per parallelizzare questa routine. Non tutte le sequenze sono candidate per la parallelizzazione, il che è uno dei motivi per cui questo non viene eseguito automaticamente dal compilatore;)

Se si sta eseguendo un lavoro che richiede il lavoro con il thread dell'interfaccia utente, questo è ancora potenzialmente possibile. Tuttavia, dovrai prestare la stessa cura che avresti ogni volta che hai a che fare con gli elementi dell'interfaccia utente sui thread in background e reinserire i dati nel thread dell'interfaccia utente. Questo può essere semplificato in molti casi usando la nuova API TaskScheduler.FromCurrentSynchronizationContext. Ho scritto su this scenario on my blog here.

+1

La migliore risposta finora, comunque una domanda a parte: dì che il mio loop-body esegue un'operazione IO a lunga esecuzione (richiesta di rete, database, ecc.), La classe Parallel rileva i thread sospesi/sospesi e ne avvia automaticamente una nuova? O sarà limitato al numero di core sulla macchina? – jonathanpeppers

+0

@ Jonathan.Peppers: l'utilità di pianificazione delle operazioni di default gestisce questo piuttosto bene. Inietterà del lavoro extra nella situazione. (Per impostazione predefinita, il ThreadPool utilizza molti più elementi rispetto ai thread e ridimensiona in base al carico di lavoro in modo dinamico) –

2

Come avete ipotizzato, approfittando della Parallel.For o Parallel.ForEach richiede che si avrà la possibilità di comporre il proprio lavoro in unità discrete (incarnato da sua dichiarazione lambda che viene passato al Parallel.ForEach) che può essere eseguito in modo indipendente .

+0

I problemi del mondo reale soddisfano questi criteri? In altre parole, l'applicazione media sarà in grado di utilizzare queste funzionalità parallele? – jonathanpeppers

+0

@ Jonathan: assolutamente. Date un'occhiata a questa presentazione di Scott Hanselman, dove mostra un vivido esempio di come funziona. http://channel9.msdn.com/posts/matthijs/Lap-Around-NET-4-with-Scott-Hanselman/ La dimostrazione inizia a 38 minuti, 55 secondi nel discorso, e termina alle 47:02. –

+0

A quanto pare il loro sito web ha qualche problema a saltare a 38:55, dovrò vedere tutto a casa e tornare da te. Sono ancora scettico sul fatto che daranno un buon esempio. – jonathanpeppers

0

c'è una grande discussione in risposte e commenti qui: Parallel.For(): Update variable outside of loop.

La risposta è no: le estensioni parallele non penseranno per voi. I problemi di multithread sono ancora effettivi qui. Questo è un buon zucchero sintattico, ma non una panacea.

+0

È un po 'più di un semplice zucchero sintattico.Ad esempio, è possibile specificare il grado di parallelismo e collegare una routine di annullamento che svolgerà con garbo tutti i thread. –

6

Tutti questi sono problemi legittimi e PLINQ/TPL non tentano di risolverli. È ancora compito di uno sviluppatore scrivere un codice che funzioni correttamente quando è in parallelo. Non c'è magia che il compilatore/TPL/PLINQ possa fare per convertire codice che non è sicuro per il multithreading in codice thread-safe ... devi assicurarti di farlo.

Per alcune delle situazioni che hai descritto, devi prima decidere se la parallelizzazione è anche ragionevole. Se il collo di bottiglia sarà acquisire la connessione a un database o garantire il corretto sequenziamento delle operazioni, forse il multithreading non è appropriato.

Nel caso di come TPL trasmette un enumerabile a più thread, la supposizione è corretta. La sequenza viene enumerata su un singolo thread e ogni oggetto di lavoro viene quindi (potenzialmente) inviato a un thread separato su cui agire. L'interfaccia IEnumerable<T> è intrinsecamente non threadsafe, ma TPL gestisce questo dietro le quinte per voi.

Cosa PLINQ/TPL ti aiuta a fare, è gestire quando e come spedire il lavoro a più thread. Il TPL rileva quando ci sono più core su una macchina e ridimensiona automaticamente il numero di thread utilizzati per elaborare i dati. Se una macchina ha solo una CPU/Core singola, allora TPL può scegliere di non parallelizzare il lavoro. Il vantaggio per te, lo sviluppatore, non è dover scrivere due percorsi diversi: uno per la logica parallela, uno per la sequenza. Tuttavia, la responsabilità è sempre tua per assicurarti che il tuo codice possa essere tranquillamente accessibile da più thread contemporaneamente.

Quale modello di progettazione dovrei seguire per risolvere questi problemi?

Non c'è una risposta a questa domanda ... tuttavia, una pratica generale è quella di utilizzare immutability nella progettazione dell'oggetto. L'immutabilità rende più sicuro consumare un oggetto su più thread ed è una delle pratiche più comuni nel rendere le operazioni parcellelabili. In effetti, linguaggi come F # utilizzano ampiamente l'immutabilità per consentire al linguaggio di facilitare la programmazione concorrente.

Se si utilizza .NET 4.0, è necessario esaminare anche le classi di raccolte ConcurrentXXX in System.Collections.Concurrent. Qui è dove troverai alcuni costrutti di lock lock senza grani fini che rendono più semplice la scrittura di codice multithread.

0

Questa è una domanda molto buona e la risposta non è chiara al 100%/concisa. Ti indicherò questo riferimento da Micrsoft, che espone un bel po 'di dettagli su WHEN you should use the parallel items.