Thread pool

Da Wikipedia, l'enciclopedia libera.

Un thread pool in programmazione indica un gestore software di thread utilizzato per ottimizzare e semplificare l'utilizzo dei thread all'interno di un programma.

Funzionamento generale[modifica | modifica wikitesto]

Schema generico di funzionamento di un thread pool: i task in attesa (pallini blu) vengono eseguiti in parallelo dai thread (scatole verdi). I pallini gialli indicano i task completati.

Durante la stesura di un programma è possibile, con diversi metodi, sottomettere dei task (o compiti) da eseguire al thread pool. Sarà compito del thread pool affidare il task ad un thread.
Generalmente i thread pool sono dotati di una coda interna di task in attesa e di un certo numero di thread con cui eseguirli. A seconda dell'implementazione del thread pool vengono utilizzate regole diverse per decidere quale task eseguire per primo, quanti thread attivi utilizzare e quanti thread lasciare in attesa quando la coda dei task è vuota.

Un concetto ricorrente nei thread pool è il riutilizzo dei thread: un thread viene usato più volte per diversi task durante il suo ciclo di vita. Questo diminuisce l'overhead dovuto alla creazione dei thread e incrementa le prestazioni del programma che sfrutta il thread pool. Il riutilizzo dei thread non è una regola, ma è uno dei principali motivi che portano un programmatore a utilizzare un thread pool nei suoi applicativi.

Implementazione e funzionamento[modifica | modifica wikitesto]

Una implementazione di thread pool è composta di solito da una coda di task e da un insieme di thread, entrambi inizialmente vuoti.
I parametri di funzionamento tipici sono i seguenti:

  • la lunghezza della coda di task (può essere virtualmente infinita);
  • il numero di thread che rimangono in vita in assenza di task;
  • il numero massimo di thread che possono essere creati (può essere virtualmente infinito[1]).

Una volta che il thread pool è stato inizializzato, è possibile richiedere che vengano creati subito i thread minimi, cioè quelli che rimarrebbero in vita anche in assenza di task[2]. Spesso viene sfruttata questa funzione se si prevede un utilizzo intensivo del thread pool e si vuole una certa reattività nell'esecuzione dei task.
A questo punto è possibile chiedere al thread pool di eseguire dei task: si possono aggiungere alla coda interna un qualsiasi numero di compiti indipendenti[3] fra di loro, senza interrompere il flusso principale del programma che sfrutta il thread pool.
Se la coda di task è infinita, il task viene sempre accettato, in caso contrario potrebbe venir rifiutato se la coda fosse piena. Ogniqualvolta il thread pool accetta un task, controlla se ci sono dei thread liberi in attesa e in tal caso assegna ad uno di essi il task. Se non ci sono thread liberi controlla se è possibile crearne uno nuovo: se è stato raggiunto il numero massimo di thread, il task viene messo in coda, altrimenti ne viene creato uno nuovo e gli viene assegnato il task.
In alcune implementazioni la coda dei task può essere a priorità o non esistere (cioè avere lunghezza 0). Nel primo caso l'estrazione di un task dalla coda è dipendente dalla sua priorità, nel secondo caso il task viene accettato solo se può essere mandato in esecuzione immediatamente.
Quando un thread ha finito di eseguire un task passa al prossimo compito in coda, se presente. Se non ci sono task in attesa il thread stesso controlla se è in esubero: se ci sono più thread attivi di quelli minimi (che possono rimanere in attesa di compiti) esso termina e conclude il suo ciclo di vita, altrimenti si mette in attesa.
Un thread pool generalmente ha anche un metodo per aspettare la conclusione dei task assegnati o per chiudere forzatamente tutti i thread che sono ancora in vita scartando i task in coda.

Implementazioni differenti potrebbero prevedere comportamenti diversi, ma lo schema generico è quello presentato finora.

Vantaggi[modifica | modifica wikitesto]

Utilizzare un thread pool nativo o implementato in una particolare architettura comporta un incremento di prestazioni da parte delle applicazioni che lo sfruttano: i thread pool ottimizzano l'utilizzo della memoria e del processore, diminuendo l'overhead di gestione dei thread. È possibile comunque adeguare il thread pool alle proprie esigenze, nel caso i parametri standard non sia soddisfacenti. Oltretutto la parallelizzazione di alcune operazioni porta ad un'ottimizzazione delle risorse e ad un incremento di velocità nello svolgimento dei compiti, anche se introduce altri tipi di problemi come l'accesso esclusivo a risorse condivise (problema di race condition).
Un thread pool permette di snellire il codice ed evitare di preoccuparsi della creazione e della terminazione dei thread, in special modo quando non se ne conosce la quantità.

Architetture che utilizzano i thread pool[modifica | modifica wikitesto]

I thread pool sono utilizzati in quasi tutte le applicazioni server, in cui c'è l'esigenza di trattare più richieste contemporanee da parte di un numero imprecisato di client.
Molte altre applicazioni però hanno bisogno che ogni task venga eseguito istantaneamente o che si abbia un controllo maggiore sul thread che lo esegue: in questo caso il thread pool non è la scelta migliore.

Linguaggi che implementano i thread pool[modifica | modifica wikitesto]

Esempi[modifica | modifica wikitesto]

Di seguito sono riportati alcuni esempi di utilizzo dei thread pool nei principali linguaggi di programmazione che li supportano nativamente.

Java[modifica | modifica wikitesto]

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
 
public class Esempio {
	public static void main(String[ ] args) {
		//Creo un nuovo thread pool.
		ExecutorService threadpool = Executors.newCachedThreadPool();
 
		//Metto in coda il task
		threadpool.execute(
	            //Questo oggetto rappresenta il task.
                    new Runnable() {
			public void run() {
				System.out.println("Vengo eseguito dal thread pool.");
			}
		});
 
                System.out.println("Il thread principale prosegue e poi si mette in attesa.");
		threadpool.shutdown();
                System.out.println("Il thread principale esce.");
	}
}

Visual Basic .NET[modifica | modifica wikitesto]

Imports System 
Imports System.Threading 
 
Public Class Example 
 
	<MTAThread> _ 
	Public Shared Sub Main() 
		' Metto in coda il task. 
		ThreadPool.QueueUserWorkItem( _ 
		        New WaitCallback(AddressOf ThreadProc) _ 
		        )
 
		Console.WriteLine("Il thread principale prosegue e poi si mette in attesa.") 
		Thread.Sleep(1000) 
 
		Console.WriteLine("Il thread principale esce.") 
	End Sub 
 
	' Questa procedura rappresenta il task.
	Shared Sub ThreadProc(stateInfo As Object) 
		Console.WriteLine("Vengo eseguito dal thread pool.") 
	End Sub 
End Class

C# .NET[modifica | modifica wikitesto]

using System; 
using System.Threading; 
public class Example { 
	public static void Main() { 
		// Metto in coda il task. 
		ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc)); 
 
		Console.WriteLine("Il thread principale prosegue e poi si mette in attesa.");
		Thread.Sleep(1000); 
 
		Console.WriteLine("Il thread principale esce."); 
	} 
 
	// Questa procedura rappresenta il task.
	static void ThreadProc(Object stateInfo) { 
		Console.WriteLine("Vengo eseguito dal thread pool."); 
	} 
}

C++ .NET[modifica | modifica wikitesto]

using namespace System; 
using namespace System::Threading; 
ref class Example 
{ 
public: 
 
	// Questa procedura rappresenta il task.
	static void ThreadProc( Object^ stateInfo ) 
	{ 
		Console::WriteLine( "Vengo eseguito dal thread pool." ); 
	} 
 
}; 
 
int main() 
{
	// Metto in coda il task. 
	ThreadPool::QueueUserWorkItem( gcnew WaitCallback( Example::ThreadProc ) ); 
	Console::WriteLine( "Il thread principale prosegue e poi si mette in attesa." ); 
 
	Thread::Sleep( 1000 ); 
	Console::WriteLine( "Il thread principale esce." ); 
	return 0; 
}

J# .NET[modifica | modifica wikitesto]

import System.*; 
import System.Threading.*; 
import System.Threading.Thread; 
 
public class Example 
{ 
	public static void main(String[] args) 
	{ 
		// Metto in coda il task. 
		ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc)); 
		Console.WriteLine("Il thread principale prosegue e poi si mette in attesa."); 
 
		Thread.Sleep(1000); 
		Console.WriteLine("Il thread principale esce."); 
	} //main 
 
	// Questa procedura rappresenta il task.
	static void ThreadProc(Object stateInfo) 
	{
		Console.WriteLine("Vengo eseguito dal thread pool."); 
	} //ThreadProc 
} //Example

Note[modifica | modifica wikitesto]

  1. ^ È sconsigliato non limitare questo numero, perché un numero infinito vanifica la natura del thread pool che è quella di ottimizzare l'utilizzo dei thread.
  2. ^ È possibile che non sia previsto un numero minimo di thread da lasciare attivi.
  3. ^ Non ci sono garanzie su quando verrà eseguito un task e questo rende pericoloso inserire dei task che dipendono fra di loro, in quanto si potrebbe bloccare la coda (generando un deadlock)

Voci correlate[modifica | modifica wikitesto]

Collegamenti esterni[modifica | modifica wikitesto]

Informatica Portale Informatica: accedi alle voci di Wikipedia che trattano di Informatica