Parallelismo nelle applicazioni ASP.NET

by Marco 6. marzo 2010 13.33

A tutti coloro che sviluppano applicazioni ASP.NET o librerie che possono essere usate in applicazioni di questo tipo, consiglio la lettura di un post molto interessante scritto dal team delle TPL:

http://blogs.msdn.com/pfxteam/archive/2010/02/08/9960003.aspx

il succo del discorso è il fatto che frameworks come asp.net sono già “paralleli” e “scalabili” by-design e che l’introduzione di ulteriore parallelismo potrebbe portare ad un degrado delle performance. Questa affermazione nasce dal fatto che solitamente un’applicazione web è tanto più “perfomante” tante più richieste al secondo riesce a soddisfare. Il runtime di asp.net cerca di sfruttare al massimo le risorse che ha disposizione, come ad esempio utilizzare tutti i core dei nostri server. Chiaramente introdurre ulteriore parallelismo in un ambiente che fa già uso di tutte le risorse di “calcolo” possibili non fa altro che aggiungere latenza agli altri utilizzatori di tale risorse, nel caso di asp.net significa che il runtime dovrà “condividere” i core con altre attività che richiedono la loro parte di potenza di calcolo. Tutto questo significa che il parallelismo non è sempre positivo, ma va usato con “attenzione”, a seconda dell’ambiente in cui si sta operando. Nel caso di ASP.NET l’introduzione di codice parallelo/asincrono ha senso se l’attività che stiamo facendo comprende attività di I/O, in questo modo liberiamo risorse(il thread che ha effettuato la richiesta di I/O; tanto la testina dei dischi o il web service che abbiamo chiamato possono lavorare senza che li dobbiamo per forza aspettare, questo diminuisce il numero di thread richiesti e abbassa il context switch)che possono essere utilizzate per fare altro(soddisfare nuove richieste). Nel caso in cui l’introduzione del parallelismo sia semplicemente dovuta a bisogni computazionali, le performance per una applicazione web ad alto carico(tante richieste al secondo) potrebbero peggiorare vistosamente, dato che i worker thread che si occupano di soddisfare le richieste devono anche essere impiegati per la computazione parallela(questo sottrare risorse per le richieste in ingresso). In questi casi potrebbe risultare più perfomante delegare a qualche altra risorsa come a un server di calcolo dedicato il compito di rendere il calcolo più performante(pagando il prezzo della chiamata out-of-proccess, chiaramente dobbiamo misurare e capire se ne vale la pena). La problematica non resta isolata allo sviluppo di applicazioni web, ma pensiamo allo sviluppo di una libreria che fa uso calcolo parallelo che viene poi utilizzata dalle nostre applicazioni web, il problema si ripresenta. Sarebbe utile avere la possibilità di specificare prima dell’utilizzo della libreria il “grado di parallelismo” richiesto.

Vi lascio alla lettura del post che riporta un bel diagramma di flusso che tutti dovremmo consultare prima di agire!

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

.Net | Concurrent Programming | Multithreading | Parallel Programming

Axum al PDC

by Marco 5. gennaio 2010 12.26

Ho già parlato in passato di questo “incubation project” e vi consiglio di guardare questa sessione di Niklas Gustafsson che illustra in modo molto chiaro il motivo per cui è necessario avere un linguaggio “ad hoc” per poter sviluppare in modo corretto/produttivo applicazioni parallele altamente scalabili sulle piattaforme multicore di oggi e domani.

Certamente non tutto il codice che scriviamo deve per forza essere parallelo, una GUI, una logica di business, il codice di accesso ai dati, non hanno sempre questi requisiti, ma vi sono alcune parti di un programma in cui questa possibilità può rendere l’applicazione molto più soddisfaciente.

Axum è un linguaggio che implementa il parallelismo attraverso il paradigma di comunicazione denominato “Message-Passing” dove l’idea principale è quella di avere degli agenti(concettualmente delle classi che fanno qualcosa) che operano su dei dati che definiscono il dominio(ad esempio le classi che contengono i dati su cui fare qualcosa). La chiave di tutto è il fatto che gli agenti possono scambiare tra loro dati solo attraverso messaggi che trasportano dati che possono essere usati in un dominio. Questa constraint da la possibilità di specificare che un agente ha la possibilità di modificare i dati di un dominio, operazione preclusa agli altri agenti che scambiano dati attraverso “copie” e non attraverso riferimenti ai dati stessi.

Gli elementi principali di Axum sono:

-Agenti: gli utilizzatori dei dati del dominio(possono stare in-process o out-of-process, permettendo così di avere un "distributed programming model").

-Dominio: i dati che vengono usati dagli agenti(in modo esclusivo ad esempio)

-Channel: l’interfaccia di comunicazione tra un agente e gli altri agenti

-Schema: definiscono le constraints sui channels e sui messaggi che vengono scambiati dagli agenti

Questo stile di programmazione permette di superare un bel po' di problemi a cui dobbiamo far fronte quando scriviamo un’applicazione che deve “andare in parallelo” ed essere diciamo “asincrona” e “scalabile”. I problemi fondamentali sono quelli dell’isolamento degli stati(evitare lo sharing di parti di memoria attraverso copie e non riferimenti diretti a questa), la sincronizzazione e locking(è un task complesso e pieno di insidie lasciamolo fare ad un runtime) , consumo di risorse(ogni thread in .NET consuma 1MB minimo), scalabilità(sfruttamento di multicore, minimizzare il context switch tra i thread etc…), ma ce ne sono anche altri meno macroscopici.

Il progetto è ancora nei Labs di Microsoft…speriamo non uccidano il bambino nella culla.

Buona visione http://microsoftpdc.com/Sessions/VTL02

P.S.: buon 2010 a tutti.

Correntemente valutato 5.0 da 1 utenti

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

.Net | Concurrent Programming | Parallel Programming

Windows HPC Server 2008 (High Performance Computing)

by Marco 2. dicembre 2009 19.17

Se avete “seriamente” bisogno di “performance” nelle vostre applicazioni vi consiglio di guardare questa sessione del pdc 2009 su Windows HPC Server 2008.

Praticamente questa estensione al sistema operativo Windows Server 2008(x64) permette di creare un cluster di server su cui distribuire calcoli. Nella sessione si vede come è possibile semplicemente aggiungendo qualche riga di codice .NET, permettere ad un servizio WCF(attraverso le HPC SOA Api) di essere utilizzato sfruttando il parallelismo del cluster, senza che il client debba sapere l’effettivo endpoint, ma usandone uno solo fornito dall’infrastruttura(il sistema prevede un broker che coordina le chiamate verso il cluster).

Come spiegato nel video questa tecnologia permette impostazioni e possibilità più “fini” confronto le classiche tecnologie di “load balancing” e il supporto a livello di Visual Studio e .NET framework rende il tutto molto appetibile e a basso impatto per uno sviluppatore “medio”.

E’ ancora in beta…ma non vedo l’ora di provarlo…buona visione.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Concurrent Programming | Parallel Programming

Ottimizzazioni del compilatore e multithreading: loop invariant

by Marco 18. novembre 2009 10.58

Codice(C#,VS2008,.NET 3.5):

public class LoopInvariant
   {
       int count = 0;
       public void Cicla()
       {
           while (this.count == 0) {}
       }
       public void Cambia()
       {
           this.count = 1;
       }
   }

fino a qua, nulla di particolare, questa classe non fa un bel niente, semplicemente abbiamo 2 metodi, un metodo Cicla() che esegue una while verificando che count sia uguale a 0, poi abbiamo Cambia() un metodo che cambia il contatore e lo porta a 1.

Ora usiamo questa classe(console application):

static void Main(string[] args)
       {
           LoopInvariant var = new LoopInvariant();
           ThreadPool.QueueUserWorkItem(o =>
           {
               System.Threading.Thread.Sleep(5000);
               ((LoopInvariant)o).Cambia();
               Console.WriteLine("Valore modificato");
           }, var);
           var.Cicla();
           Console.WriteLine("Fine test");
           Console.ReadKey();
       }

qua cosa succede; niente di particolare: istanzio la classe LoopInvariant, faccio partire in un thread separato(attraverso il threadpool di .NET) un pezzo di codice che si blocca per 5 secondi(Sleep(5000)) dopo di che chiama il metodo Cambia() sulla mia istanza e stampa che lo ha fatto.

Bene, compiliamo(in release). Lanciamo(CTRL+F5)…passano i 5 secondi…6…7….8….9….?!?!?!?!?

LoopInvariant

cavolo succede?perchè non stampa “Fine test”?ho cambiato il valore di count…dovrebbe stampare “Fine test” (visto che la condizione count == 0 è falsa) e fermarsi li ad aspettare la pressione di un tasto…

Il problema è che il Jit compiler analizzando il metodo Cicla() applica una ottimizzazione chiamata Loop-invariant code motion, ovvero dal fatto che la variabile count non viene “toccata” all’interno del corpo del while, il compilatore può concludere che count è “loop invariant” e così metterlo in una variabile temporanea prima di iniziare il loop(in un registro del processore magari, così evitiamo di dover risolvere l’indirizzo in ram della variabile ad ogni ciclo migliorando le prestazioni) , questo comporta che quanto l’altro thread chiama Cambia() sulla stessa istanza la modifica non viene “vista” dal nostro metodo Cicla() portanto ad un loop infinito. Cosa che genera ancora più confusione è che se facciamo il debug(F5) di questo codice tutto funziona correttamente in quanto le ottimizzazioni sono disabilitate in configurazione DEBUG.

Per disabilitare questa ottimizzazione occorre dichiarare la nostra variabile count come volatile, questo rende il nostro count esplicitamente non “loop invariant” , come Joe Duffy spiega nel suo libro:

“Load and stores of volatile variables can never be introduced or removed, both in .NET and VC++, because they are assumed to be constantly changing. As such, they aren’t eligible for being considered loop invariant and hoisted outside of loops…”

quindi basta cambiare il nostro codice in questo modo:

public class LoopInvariant
   {
       volatile int count = 0;
       public void Cicla()
       {
           while (this.count == 0) {}
       }
       public void Cambia()
       {
           this.count = 1;
       }
   }

a questo punto se compiliamo(sempre in release altrimenti le ottimizzazioni vengono soppresse per supporto al debug) e lanciamo(CTRL+F5)…1…2….3..4..5…e

LoopInvariant2

adesso il programma funziona come ci aspettavamo…

Attenzione questo non è il solo effetto della keyword volatile, ricordo che leggere da un campo volatile(o usare Thread.VolatileRead) equivale logicamente ad una acquire fence, mentre scrivere un campo volatile(o usare Thread.VolatileWrite) equivale logicamente ad una release fence.

La classe LoopInvariant chiaramente non serve ad un piffero ed è li solo per didattica, ma questo ci fa capire che la programmazione multithread “aggiunge” una dimensione che oggi non è gestita in modo automatico dai compilatori, nel caso della loop invariant fare quella ottimizzazione non cambiava la semantica del programma, ma il danno lo abbiamo visto tutti.

Occhio…

Fonti: Concurrent Programming on Windows

Correntemente valutato 5.0 da 1 utenti

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Concurrent Programming | Multithreading | Parallel Programming

Fences/Barriers in teoria…

by Marco 9. settembre 2009 18.34

Dopo aver letto questo mio precedente post , dovrebbe essere chiaro come sia possibile che stores/loads possano essere riordinati nel rispetto delle regole del memory model in cui il nostro codice gira.

Se ci pensiamo un attimo, la cosa non dovrebbe toccarci, nel senso che se il compilatore/processore aggiunge/toglie/sposta istruzioni per rendere tutto più performante a noi dovrebbe andare più che bene. Questo è effettivamente vero finchè non facciamo uso di tecniche di programmazione “lock free”, ovvero quando non viene fatto uso di primitive di sincronizzazione(lock(…),Monitor,Events,Mutex) per sincronizzare l’accesso a zone di memoria condivise da più thread. Questa tecnica, rende molto più scalabili e performanti le applicazioni multithread su più processori, in quanto il fatto di non mettere mai il thread in “wait” permette di avere un “throughput” maggiore a confronto di tecniche che utilizzano i lock(nessun thread “blocca” nessun altro thread, se non a basso livello, ma è trasparente a noi e viene fatto dal processore, niente context switch insomma). 

Perchè dovrebbero essere un problema quindi queste ottimizzazioni?
Perchè, mentre muovere istruzioni dal punto di vista di un solo thread quindi di un solo percorso di esecuzione non da nessuna inconsistenza, quando sono più di uno i thread che devono eseguire questo percorso in modo parallelo/contemporaneo il riordinamento di una store/load potrebbe inficiare il corretto funzionamento dell’algoritmo portando a inconsistenze e a difficili bugs da scovare/risolvere.

Per poter avere il controllo sul riordinamento(ottimizzazione) fatto da processore/compilatore, possiamo usare delle istruzioni cosiddette fences/barriers(recinto/barriera), attraverso le quali possiamo indicare al processore/compilatore quali tipi ottimizzazioni(spostamenti) sono possibili.

Ricordiamoci che le ottimizzazioni possono essere fatte a qualsiasi livello dello stack software, quindi sia a livello di compilazione che a livello di esecuzione(processore), mettere delle fence a livello di codice(compilatore) non significa metterle anche a livello di processore, tutto questo dipende dal runtime su cui state sviluppando, es:.NET, VC++, C etc..

Detto questo esistono vari tipi di fence(da ora in poi li chiamerò così e non barrier, ma sono la stessa cosa, dipende dal runtime/ambiente in cui state lavorando):

Full fence: nessuna stores o loads possono essere spostate al di là della fence, in tutte e due le direzioni(molte architetture mettono ad disposizione delle istruzioni come ad esempio MFENCE) quindi(pseudo codice):

load A
load B
store A

Fence

store B
store C
load C

le istruzioni stores/loads ABA e BCC non possono essere spostate al di là della fence in nessuna delle due direzioni, le istruzioni dopo la fence non possono muoversi prima, quelle prima non possono spostarsi dopo. Le full fence sono quelle più “rigide” e tutti gli altri tipi vengono usati per non sacrificare troppo le ottimizzazioni.

Store fence: simili alle full fence eccetto che si applicano solo alle istruzioni di stores e permettono lo spostamento delle loads. Quindi(pseudo codice):

load A
load B
store A

Store fence

store B
store C
load C

Le istruzioni loads A,B,C possono essere spostate prima/dopo della fence(questa fence è disponibile su x86 e x64 e viene generalmente esposta con l’istruzione SFENCE)

Load fence: sono il perfetto contrario delle store fence(e vengono comunemente espresse con l’istruzione LFENCE)

load A
load B
store A

Load fence

store B
store C
load C

Le istruzioni di stores A,B,C possono essere spostate prima/dopo della fence.

Come abbiamo detto prima le fence possono essere utilizzate a livello di processore(full fence, store fence, load fence), ma anche a livello di compilazione:

Acquire fence: assicurano che ne le stores ne le loads che ci sono dopo la fence possano essere spostate prima di questa. Le istruzioni che vengono prima possono essere spostate dopo.

load A
load B
store A

Acquire fence

store B
store C
load C

store B,c e load C non possono essere spostate prima della fence.

Release fence: assicurano che ne le stores ne le loads che ci sono prima della fence possano muoversi dopo della stessa. Le istruzioni dopo la fence possono essere spostate prima

load A
load B
store A

Release fence

store B
store C
load C

load A,B e store A non possono essere spostate dopo la fence.

Queste ultime due fence vengono usate dai complilatori e dall’architettura IA64, e hanno la peculiarità di essere applicate solo alla direzione e non al tipo di istruzione.

Dobbiamo assicurarci di applicare la fence sia a livello di compilatore che a livello di processore, in quanto sono 2 layer separati che possono in modo indipendente effettuare dei riordinamenti(in gergo si dice “move” o meglio “move after..” “move before..”).

Esistono vari modi di introdurre delle fence nel nostro codice e questo varia dalla piattaforma che stiamo usando(.NET,VC++, C), come ad esempio il marcare “volatile” una variabile con .NET o usare delle macro in VC++, ma ce ne sono altri e questo è argomento per un’altro post.

Per concludere è bene sottolineare che il riordinamento che viene effettuato a livello di compilatore/processore, rispetta sempre la “data dependence”(dipendenza dei dati) per non provocare errori logici durante le stores/loads(altro argomento un pò complesso che è bene trattare separatamente).

Fonti: Concurrent programming on Windows(Joe Duffy)

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Concurrent Programming | Multithreading | Parallel Programming

Loads e Stores

by Marco 7. settembre 2009 12.12

Nel precedente post sui memory model ho parlato spesso di loads e stores.

Si ok, ma cosa vuol dire?

Allora quando parliamo di loads si intendono quelle istruzioni che spostano i dati da una locazione di memoria in un registro del processore, mentre le stores sono quelle istruzioni che spostano i dati da un registro del processore ad una locazione di memoria.

In una architettura load/store, le istruzioni di load e store solo le uniche istruzioni che accedono ai dati in memoria.

Esempio ad alto livello:

x = 1  Store, il valore 1 verrà spostato da un registro del processore all’indirizzo in memoria x
y = x Load, il valore in memoria x verrà spostato in un registro  del processore per essere utilizzato

Così…per chiarezza.

Fonti: Concurrent Programming on Windows( Joe Duffy)

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Multithreading | Parallel Programming | Concurrent Programming

Cosa si intende per Memory Model?

by Marco 1. settembre 2009 17.03

Se vi è per caso capitato di approfondire, per qualche motivo, tematiche legate all’implementazione di soluzioni che fanno uso di codice parallelo/multithread/sincronizzazione probabilmente avete finito leggendo che vi è la possibilità di aumentare le performance usando tecniche di sincronizzazione “lock free”, ovvero senza ricorrere alle primitive di sincronizzazione messe a disposizione dal sistema operativo(che consumano risorse e vanno usate con attenzione).

Molto probabilmente qualche riga sotto l’espressione “lock free” avrete sicuramente trovato che tutto questo è possibile grazie al “Memory consistency model” (anche detto semplicemente memory model) implementato dall’hardware su cui il vostro codice gira.

Ma cos’è il memory model ?Perchè è importante conoscerlo ?

Per spiegarlo facciamo un passo indietro. Quando noi scriviamo codice sorgente ci immaginiamo che l’esecuzione dello stesso sarà così come l’abbiamo scritta. Purtroppo nei moderni processori non è così, nel senso che l’esecuzione del codice macchina potrebbe risultare diversa da quella da noi pensata anche senza inficiare il corretto funzionamento dei nostri algoritmi.

Le motivazioni principali sono:

1)I compilatori ottimizzano (Jit compiler compreso) il codice, spostando (code motion),cancellando, aggiungendo istruzioni per renderne più performante l’esecuzione.
2)I processori impiegano tecniche di “instruction level parallelism” (ILP) permettendo la parallelizzazione delle istruzione così da ridurre i cicli di clock totali.
3)I processori fanno uso di cache locali per velocizzare la scrittura (stores) e lettura (loads) delle locazioni di memoria, in sistemi multiprocessore la sincronizzazione tra le varie cache può portare ad uno spostamento delle istruzioni(cache coherency).

Tutto questo passa sotto il nome di instruction reordering.

Chi scrive codice a basso livello o compilatori o codice lock free, deve conoscere molto bene questi comportamenti per poter effettuare ottimizzazioni e far funzionare come si deve il codice (cioè come noi lo abbiamo scritto o senza provocare race condition).

Per concludere il memory model specifica precisamente quali tipi di scritture (stores) e letture (loads) possono essere spostate (o riordinate), in quali circostanze e come vengono spostate l’una rispetto all’altra.

I memory model si dividono tra weak (deboli) o strong (forti). Un “weak memory model” permette il riordinamento delle letture/scritture, un “strong memory model” (sequential consistency) proibisce tale riordinamento. Molti sistemi come quelli dal quale sto scrivendo questo post (architettura Intel x86) e la maggior parte dei sistemi sul quale Windows gira utilizzano un “weak memory model”.

Fonti: Concurrent Programming on Windows (Joe Duffy)

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Multithreading | Parallel Programming | Concurrent Programming

STM.NET io voto si :-)

by Marco 27. agosto 2009 15.02

Segnalo un nuovo interessante progetto pubblicato dai DevLabs chiamato Software Transactional Memory.

Il progetto è nato con l’intento di fornire un modo “semplice” per permettere ad uno sviluppatore di dichiarare che l’esecuzione di una parte di codice debba essere eseguita in modo “atomico” e “transazionale”.

I problemi che questa nuova features risolverebbe non sono pochi e di semplice soluzione per uno sviluppatore che si appresta a scrivere codice parallelo.

Fino ad oggi se noi vogliamo sfruttare il parallelismo nel nostro codice, dobbiamo fare molta attenzione agli “stati condivisi” e all’ ”invarianza” che precluderebbero il buon funzionamento dei nostri algoritmi.

Oggi per fare questo ci possiamo affidare a delle tecniche di locking come ad esempio un bel Monitor, es(il codice è al minimo per chiarezza e non tiene conto di eventuali stati di errore che potrebbero portare a lock orfani, deadlock etc…):

Monitor.Enter(obj);

…//codice atomico

Monitor.Exit(obj);

Il codice non è complesso, ma come ho indicato tra parentesi i problemi che possono nascere sono molti e non così semplici e scontati da anticipare. Inoltre il nostro bel monitor(l’oggetto obj) deve essere trattato con cura onde evitare ulteriori danni(deadlock per esempio).

Come se non bastasse, il secondo problema non è legato al parallelismo di per sè, ma dalla parte di codice che sta tra l’Enter e l’Exit(critical region). In caso il codice al suo interno vada in errore potrebbe succedere che venga meno l’invarianza, cioè che il sucessivo thread che entra in quella regione atomica trovi stati logici inconsistenti precludento il corretto funzionamento del codice, es(il codice non è completo…fate finta che sia correttamente inserito in un try…catch):

Monitor.Enter(obj);

Decimal saldoConto = corrente – addebito;

//quì ad esempio potrebbe scatenarsi un’eccezzione che invalida le righe di codice sopra

Monitor.Exit(obj);

Come potete vedere questo errore è di una gravità mostruosa(sempre che non siate molto ricchi :-D) e solitamente prima di effettuare le operazioni servirebbe del codice che controlla che tutto sia coerente, ma questo non è sempre facile e dipende dalla problematica, che può essere molto complessa con molte casistiche da verificare.

Quello che STM potrebbe risolvere in modo stra-elegante sono proprio queste due problematiche, tutto con un nuovo semplice metodo(con il proprio statement shortcut) e una modifica al funzionamento del Jitter(quindi un framework con delle modifiche interne) es:

Atomic.Do(()=> { <statememts> });

o

atomic { <statememts> }

In questo modo non dobbiamo preoccuparti di trattare bene il nostro monitor(il rifermento all’oggetto obj del metodo Enter), ma la cosa più furba è il fatto che un errore nel blocco causa il rollback di tutte le modifiche fatte in quello scope mantenendo così l’invarianza senza dover fare controlli.

Vi lascio con un esempio preso dalla guida che rende tutto molto più chiaro:

class BankAccount
{
        private int m_balance;
        public void ModifyBalance(int amount) {
       atomic {
                       m_balance = m_balance + amount;
                       if (m_balance < 0)
                               throw new OverdraftException(m_balance, amount);
                  }
        }
}

Spettacolo!!!

Spero vivamente che il progetto non vada nel dimenticatoio, sarebbe una manna per tutti noi.

Qui trovate il portale del progetto.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.Net | Multithreading | Parallel Programming | Concurrent Programming

.NET 4.0 Cancellation Framework, ottima idea

by Marco 16. giugno 2009 11.37

Capita non di rado in alcune applicazioni di avere la possibilità di interrompere qualche operazione “lunga” o “asincrona” a fronte della richiesta di qualche operatore sia esso un’utente comune o un agente software. Questa operazione spesso sottovalutata dovrebbe essere conclusa senza portare il sistema in uno stato inconsistente in tutti i sensi, per tutti i sensi intendo sia dal punto di vista “tecnico”(es:stabilità del processo, risorse etc..), sia dal punto di vista dello stato dei dati in questione(es:una griglia su un form).

Questo significa che qualunque sia il tipo di operazione da interrompere, questa deve essere conscia del fatto che qualcuno ha deciso che deve smettere di fare quello che sta facendo senza provocare danni.

Con questo problema si sono dovuti scontrare chiaramente il team delle Task Parallel Library(.NET 4.0), riguardo al problema di dover interrompere un’operazione asincrona/parallela.

A fronte di questo hanno deciso di creare un’insieme di classi preposte alla gestione delle “cancellazioni” di operazioni in modo “cooperativo”, tutto questo sotto il nome di  “Cancellation Framework”.

Le classi principali che compongono questo framework sono: CancellationToken che verrà passata all’operazione “interrompibile” per informarla che qualcuno ha richiesto uno stop, e CancellationTokenSource che scatenerà l’interruzione.

La cosa molto interessante è la modalità di comunicazione tra CancellationTokenSource e CancellationTokens (potrebbero essere più di uno, potremmo richiedere lo stop a più operazioni asincrone/parallele contemporaneamente). Possiamo effettuare un polling sulla proprietà IsCancellationRequested dell’oggeto CancellationToken o registrare una callback che verrà eseguita alla richiesta della cancellazione.

Questi tipi di pattern sono già stati implementati in molte classi del framework, ma ogni implementazione ha sempre avuto un suo set di classi personalizzate. L’idea di avere un framework comune mi sembra ottima, sarebbe molto più facile scrivere codice più “generico” e adattabile a contesti diversi, oltre che corretto nella sua implementazione (ci sono molte cose non banali da gestire, la sincronizzazione etc…).

Per una più dettagliata spiegazione dell’argomento vi lascio il post del pfxTeam.

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

.Net | Multithreading | Parallel Programming

Parallel.For/ForEach e i System.Threading.Task

by Marco 9. giugno 2009 14.08

Il concetto che per avere migliori performance dobbiamo usare meno thread fisici possibili e di conseguenza meno risorse( magari 1 thread per core ), viene usato anche all’interno delle nuove Task Parallel Library ( .NET Framework 4.0 ) riguardo agli oggetti Task.

Cio significa che se abbiamo un ciclo for parallelo ( Parallel.For/ForEach ), non è detto che per ogni iterazione avremo un’oggetto Task che verrà schedulato, ma l’odierna implementazione prevede che ogni Task processi un chunk di operazioni ( iterazioni diciamo ), così da abbassare l’overhead della creazione e gestione degli stessi.

Ciò potrebbe portare a subdoli problemi, come nel caso in cui ci siano delle dipendenze tra le iterazioni che potrebbero mandare in deadlock il loop.

Se vi interessa un’approfondimento sulla tematica vi consiglio di dare una letta a questo post del pfxTeam :

http://blogs.msdn.com/pfxteam/archive/2009/05/26/9641563.aspx

“Axum” e il parallelismo implicito

by Marco 11. maggio 2009 16.13

Dai laboratori di ricerca di Microsoft è nato il progetto “Axum”, un’idea ambiziosa e molto interessante nata con lo scopo di permettere a noi sviluppatori di scrivere codice lasciando che il runtime possa “capire” e parallelizzare in modo autonomo l’esecuzione dello stesso, attraverso un linguaggio che si basa sull'architettura Web e sul principio dell'isolamento(fondamentale nella programmazione parallela).

Vi consiglio di dare un’occhiata al video che trovate nella pagina principale di presentazione del progetto al link http://msdn.microsoft.com/en-us/devlabs/dd795202.aspx

Credo che il “parallelismo implicito” sia l’unico modo per permettere a tutti di sfruttare veramente la potenza che i multicore ci mettono a disposizione senza dover leggere troppi libri :-)

Vota questo post per primo

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , ,

Multithreading | Parallel Programming

In attesa delle TPL: Parallel.For me ;-)

by Marco 16. febbraio 2009 16.04

In attesa delle Task Parallel Library che saranno presenti nella versione 4.0 del Framework e che ora sono in CTP mi sono servito della sintassi che utilizzeremo in futuro per esprimere un ciclo For parallelo.

La firma di uno degli overload del For parallelo delle TPL ad oggi è questo:

public static void For(int fromInclusive,int toExclusive,Action<int> body)

Praticamente possiamo eseguire un’azione (Action<int> body) in parallelo più volte finchè la condizione fromInclusive < toExclusive viene verificata.

Il parametro di tipo intero che viene passato al body è l’indice del counter.

La sintassi con cui possiamo invocare il for parallelo è ad esempio questa:

System.Threading.Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

il codice soprastante funziona se avete scaricato la CTP del giugno scorso e avete referenziato l’assembly System.Threading.
Il codice non fa niente di interessante al posto del Console.WriteLine(…) avreste dovuto mettere la parte di codice che volete venga eseguita in parallelo.

La mia versione del For parallelo fa uso della classe ThreadPool presente nel framework e che possiamo utilizzare già oggi, quindi utilizza un differente algoritmo di scheduling e non quello utilizzato dalle nuove TPL (molto più fine e complesso, magari vi spiegherò in qualche post futuro come funziona)…diciamo che hanno scritto qualche riga in più :-) e che risolvono molti più problemi di quelli che la mia classe risolve. Quindi il funzionamento beneficia delle gioie e soffre i dolori che il funzionamento della classe ThreadPool porta con se.

La classe che ho scritto e utilizzato è la seguente:

public class Parallel
{
    public static void For(int fromInclusive, int toExclusive, System.Action<int> body)
    {
        ThreadPool.QueueUserWorkItem(f =>
        {
            for (int i = fromInclusive; i < toExclusive; i++)
            {
                ThreadPool.QueueUserWorkItem(p =>
                    {
                        Data pl = (Data)p;
                        pl.Action(pl.Count);                   
                    }
                    , new Data() { Action = body, Count = i });
            }
        }, null);       
    }  
    class Data
    {
        public Action<int> Action { get; set; }
        public int Count { get; set; }
    }
}

Il codice di invocazione è il medesimo delle TPL basta evitare di usare la parte System.Threading, ma scrivere solamente:

Parallel.For(0,4,
               c =>
               {                                       
                   Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);                   
               });

la classe deve essere chiaramente visibile.
Il codice non è stato testato "seriamente" quindi potrebbe essere afflitto da bugs e non tratta problematiche come ad esempio gestione delle eccezioni, sospensione del task, sincronizzazione, partizionamento o altre problematiche legate al parallelismo che sono state gestite all’interno delle TPL…per il mio problema questa classe è più che sufficiente, il giorno che rilasceranno le TPL toglierò la mia classe e avrò bisogno solo di referenziare l’assembly con le nuove classi per sfruttare il lavoro del team di Microsoft.
Se trovate qualche bugs o questa soluzione non risolve il vostro problema o ne crea di nuovi contattatemi qui che magari ne parliamo.

 Scarica la solution di esempio

Disclaimer
Le opinioni espresse in questo blog sono mie opinioni personali.

© Copyright 2010 Knowledge.CreateAsync()