CLR – Thread Pool : Introduzione

Generalmente, la creazione di un thread è molto costosa in termini di risorse da allocare ed in termini di tempo. Inoltre, avere molti thread attivi nel sistema per poter eseguire molteplici operazioni in parallelo tende a ridurre le performance del sistema stesso, in quando il processore è costretto ad eseguire numerosi context switch durante lo scheduling.

Per ottimizzare l’utilizzo delle risorse e migliorare le performance, ci viene in aiuto il thread pool fornito dal CLR. Bisogna precisare, però, che la funzionalità del thread pool è nativa in Windows e che tutte le classi ed i metodi forniti dal CLR ne rappresentano comunque un wrapper.

1727.image_5B05635A

Attraverso il thread pool, il CLR fornisce appunto un pool di thread che possiamo utilizzare per eseguire elaborazioni in parallelo. Il manager del thread pool ha a disposizione una coda all’interno della quale pervengono tutte le richieste di elaborazione parallela fatte dalla nostra applicazione. Ogni qual volta viene inserita una nuova richiesta, il manager analizza se nel thread pool è disponibile un thread libero in stato di wait e pronto per poter essere assegnato all’esecuzione di una nuova elaborazione. Nel caso affermativo, viene eseguita tale assegnazione e la computazione da noi richiesta viene eseguita all’interno di un thread che deve essere solo avviato e non creato (in quanto già esistente) con un notevole risparmio di tempo (soprattutto in fase di avvio) e di risorse (non dobbiamo allocare il contesto di un nuovo thread). Viceversa, nel caso in cui, un thread non fosse disponibile nel pool, esso viene creato (ovviamente con una riduzione di performance e maggior utilizzo di risorse) ed assegnato all’elaborazione da eseguire. Nel momento in cui, però, un thread del pool termina la sua elaborazione esso non viene eliminato ma rimesso nel pool in stato di wait e pronto per essere assegnato ad altre richieste. L’eliminazione del thread, per risparmiare risorse allocate, viene eseguita solo nel caso in cui un thread non viene utilizzato per un certo tempo definito nel CLR.

Da questo scenario si evince che i vantaggi possono essere :

  • riduzione al minimo dell’allocazione di risorse;
  • velocità nell’avvio di un’elaborazione parallela;
  • miglioramento generale delle performance;

I parametri che possono, però, influenzare l’utilizzo ottimale del pool sono molteplici ed in particolare :

  • frequenza di schedulazione : intesa come la frequenza con cui la nostra applicazione richiede l’avvio di una computazione utilizzando il thread pool. E’ ovvio che se tale frequenza è elevata, ci saranno maggiori possibilità che il manager del pool debba allocare più thread dal pool stesso considerando che la coda delle richieste tende a popolarsi rapidamente. Con una frequenza di schedulazione bassa, probabilmente avremo più possibilità di riutilizzare lo stesso thread per eseguire tutte le richieste pervenute nella coda. E’ anche vero che ciò è legato alla durata della computazione (vedi dopo);
  • durata della computazione : indica la durata dell’elaborazione che vogliamo far eseguire ad un thread del pool. Più essa è alta e più un thread rimane occupato  e quindi il manager del pool dovrà allocare e utilizzare altri thread per esaudire ulteriori richieste nella coda;
  • numero di elaborazioni parallele : inteso come il numero di richieste che la nostra applicazione fa al manager del pool per eseguire elaborazioni parallele. Maggiore è il numero di richieste e maggiore è la probabilità che verranno utilizzati più thread nel pool per esaudirle;
  • numero di core del processore : questo aspetto hardware influisce soprattutto sulle performance, considerando che pur nel caso di più thread allocati dal pool, essi saranno distribuiti su più core e quindi la velocità di esecuzione sarà notevole;

Dalle considerazioni suddette, si evince che, per ottimizzare l’utilizzo di risorse del pool è necessario eseguire delle elaborazioni molto brevi, in modo che il manager sia in grado di riciclare sempre lo stesso thread per eseguirle. E’ anche vero che in questo caso le nostre operazioni verranno “sequenzializzate”. Un miglioramento delle performance a discapito di maggiori risorse allocate, lo otteniamo soprattutto nel caso in cui le computazioni sono più lunghe, per cui il manager è costretto ad allocare più thread, e magari usufruire di un processore multicore in modo da parallelizzare “realmente” le esecuzioni.

E’ ovvio che in questi termini, possono essere fatti numerosi ragionamenti alterando in un modo o nell’altro i parametri suddetti che vanno ad influenzare l’utilizzo ottimale del pool e delle risorse di sistema.

E’ inoltre importante sottolineare che, le computazioni per le quali richiediamo un’elaborazione parallela non devono prevedere dei meccanismi di sincronizzazione o accesso a risorse condivise, altrimenti decade il miglioramento di performance dovuto al parallelismo, soprattutto in un sistema multicore.

Tornando all’implementazione del pool nel CLR, esso fornisce due tipologie di thread :

  • worker threads : utilizzati per operazioni di tipo CPU bound;
  • completion port threads : thread che vengono avviati tipicamente al termine di operazioni asincrone su file o socket. L’uso di tali thread si basa proprio sul meccanismo delle completion port fornito da Windows. Infatti, ogni qual volta viene avviata un’operazione asincrona, uno dei meccanismi forniti da Windows per segnalare il termine di tale operazione, è quello di inserire un pacchetto in una coda specifica opportunamente monitorata da un thread, che nel nostro caso sarà proprio un completion port thread nel quale sarà eseguita la callback prevista al termine dell’operazione asincrona;

Prima di terminare questa breve introduzione per poi “sporcarci” le mani negli articoli successivi, anticipo quelli che saranno i possibili campi di utilizzo del thread pool :

  • worker thread : possibilità di utilizzare un thread per eseguire un’operazione CPU bound;
  • wait event : utilizzare un thread del pool per monitorare lo stato di un evento e di far eseguire una nostra callback nel momento in cui l’evento risulta segnalato oppure un eventuale timeout è scattato;
  • timer : uso di un thread per eseguire operazioni periodiche basate appunto su un timer;
  • I/O asincrono : possibilità di lanciare operazioni asincrone su file o socket per poi demandare ad un thread del pool il compito di eseguire una callback al termine di tale operazione;
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s