Condividi tramite


Porte di completamento di I/O

Le porte di completamento di I/O forniscono un modello di threading efficiente per l'elaborazione di più richieste di I/O asincrone in un sistema multiprocessore. Quando un processo crea una porta di completamento I/O, il sistema crea un oggetto coda associato per i thread il cui unico scopo è quello di gestire queste richieste. I processi che gestiscono molte richieste di I/O asincrone simultanee possono farlo in modo più rapido ed efficiente usando le porte di completamento di I/O in combinazione con un pool di thread preallocato rispetto alla creazione di thread al momento in cui ricevono una richiesta di I/O.

Funzionamento delle porte di completamento di I/O

La funzione CreateIoCompletionPort crea una porta di completamento I/O e associa uno o più handle di file a tale porta. Quando viene completata un'operazione di I/O asincrona su uno di questi handle di file, viene accodato un pacchetto di completamento di I/O nell'ordine FIFO (First-In-First Out) alla porta di completamento I/O associata. Un uso efficace per questo meccanismo consiste nel combinare il punto di sincronizzazione per più handle di file in un singolo oggetto, anche se sono presenti anche altre applicazioni utili. Si noti che, mentre i pacchetti vengono accodati nell'ordine FIFO, potrebbero essere dequeuati in un ordine diverso.

Nota

Il termine handle di file usato qui fa riferimento a un'astrazione di sistema che rappresenta un endpoint di I/O sovrapposto, non solo un file su disco. Ad esempio, può essere un endpoint di rete, un socket TCP, una named pipe o uno slot di posta elettronica. È possibile usare qualsiasi oggetto di sistema che supporta operazioni di I/O sovrapposte. Per un elenco delle funzioni di I/O correlate, vedere la fine di questo argomento.

 

Quando un handle di file è associato a una porta di completamento, il blocco di stato passato non verrà aggiornato fino a quando il pacchetto non viene rimosso dalla porta di completamento. L'unica eccezione è se l'operazione originale restituisce in modo sincrono un errore. Un thread (uno creato dal thread principale o il thread principale stesso) usa la funzione GetQueuedCompletionStatus per attendere che un pacchetto di completamento venga accodato alla porta di completamento I/O, anziché attendere il completamento diretto dell'I/O asincrono. I thread che bloccano l'esecuzione su una porta di completamento I/O vengono rilasciati nell'ordine LIFO (Last-In-First-Out) e il pacchetto di completamento successivo viene estratto dalla coda FIFO della porta di completamento I/O per tale thread. Ciò significa che, quando un pacchetto di completamento viene rilasciato a un thread, il sistema rilascia l'ultimo thread (più recente) associato a tale porta, passando le informazioni di completamento per il completamento di I/O meno recente.

Anche se un numero qualsiasi di thread può chiamare GetQueuedCompletionStatus per una porta di completamento I/O specificata, quando un thread specificato chiama GetQueuedCompletionStatus la prima volta, diventa associato alla porta di completamento I/O specificata fino a quando non si verifica una delle tre operazioni seguenti: il thread viene chiuso, specifica una porta di completamento I/O diversa, o chiude la porta di completamento di I/O. In altre parole, un singolo thread può essere associato al massimo a una porta di completamento di I/O.

Quando un pacchetto di completamento viene accodato a una porta di completamento I/O, il sistema controlla innanzitutto il numero di thread associati alla porta in esecuzione. Se il numero di thread in esecuzione è minore del valore di concorrenza (illustrato nella sezione successiva), uno dei thread in attesa (quello più recente) può elaborare il pacchetto di completamento. Quando un thread in esecuzione completa l'elaborazione, in genere chiama GetQueuedCompletionStatus di nuovo, a quel punto restituisce con il pacchetto di completamento successivo o attende se la coda è vuota.

I thread possono usare la funzionePostQueuedCompletionStatusper inserire i pacchetti di completamento nella coda della porta di completamento di I/O. In questo modo, la porta di completamento può essere usata per ricevere comunicazioni da altri thread del processo, oltre a ricevere pacchetti di completamento di I/O dal sistema di I/O. La funzione PostQueuedCompletionStatus consente a un'applicazione di accodare i propri pacchetti di completamento speciali alla porta di completamento di I/O senza avviare un'operazione di I/O asincrona. Ciò è utile per notificare ai thread di lavoro eventi esterni, ad esempio.

L'handle di porta di completamento I/O e ogni handle di file associato a tale porta di completamento I/O specifico sono noti come riferimenti alla porta di completamento I/O. La porta di completamento di I/O viene rilasciata quando non sono presenti altri riferimenti. Pertanto, tutti questi handle devono essere chiusi correttamente per rilasciare la porta di completamento I/O e le risorse di sistema associate. Dopo aver soddisfatto queste condizioni, un'applicazione deve chiudere l'handle della porta di completamento I/O chiamando la funzioneclosehandle.

Nota

Una porta di completamento I/O è associata al processo che lo ha creato e non è condivisibile tra i processi. Tuttavia, un singolo handle è condivisibile tra thread nello stesso processo.

 

Thread e concorrenza

La proprietà più importante di una porta di completamento I/O da considerare attentamente è il valore di concorrenza. Il valore di concorrenza di una porta di completamento viene specificato quando viene creato con CreateIoCompletionPort tramite il parametro NumberOfConcurrentThreads. Questo valore limita il numero di thread eseguibili associati alla porta di completamento. Quando il numero totale di thread eseguibili associati alla porta di completamento raggiunge il valore di concorrenza, il sistema blocca l'esecuzione di tutti i thread successivi associati a tale porta di completamento fino a quando il numero di thread eseguibili scende al di sotto del valore di concorrenza.

Lo scenario più efficiente si verifica quando sono presenti pacchetti di completamento in attesa nella coda, ma non è possibile soddisfare attese perché la porta ha raggiunto il limite di concorrenza. Considerare cosa accade con un valore di concorrenza di uno e più thread in attesa nella GetQueuedCompletionStatus chiamata di funzione. In questo caso, se la coda ha sempre pacchetti di completamento in attesa, quando il thread in esecuzione chiama GetQueuedCompletionStatus, non bloccherà l'esecuzione perché, come accennato in precedenza, la coda del thread è LIFO. Al contrario, questo thread rileverà immediatamente il successivo pacchetto di completamento in coda. Non si verificheranno cambi di contesto del thread, perché il thread in esecuzione raccoglie continuamente pacchetti di completamento e gli altri thread non sono in grado di eseguire.

Nota

Nell'esempio precedente, i thread aggiuntivi sembrano essere inutili e mai eseguiti, ma presuppone che il thread in esecuzione non venga mai inserito in uno stato di attesa da un altro meccanismo, termina o chiude la porta di completamento I/O associata. Prendere in considerazione tutte queste ramificazioni di esecuzione di thread durante la progettazione dell'applicazione.

 

Il valore massimo complessivo migliore da selezionare per il valore di concorrenza è il numero di CPU nel computer. Se la transazione richiede un calcolo lungo, un valore di concorrenza più grande consentirà l'esecuzione di più thread. Il completamento di ogni pacchetto di completamento può richiedere più tempo, ma più pacchetti di completamento verranno elaborati contemporaneamente. È possibile sperimentare il valore di concorrenza insieme agli strumenti di profilatura per ottenere l'effetto migliore per l'applicazione.

Il sistema consente inoltre a un thread in attesa in GetQueuedCompletionStatus di elaborare un pacchetto di completamento se un altro thread in esecuzione associato alla stessa porta di completamento I/O entra in uno stato di attesa per altri motivi, ad esempio la funzione SuspendThread. Quando il thread nello stato di attesa inizia di nuovo l'esecuzione, può verificarsi un breve periodo in cui il numero di thread attivi supera il valore di concorrenza. Tuttavia, il sistema riduce rapidamente questo numero non consentendo nuovi thread attivi fino a quando il numero di thread attivi non scende al di sotto del valore di concorrenza. Questo è un motivo per cui l'applicazione crea più thread nel pool di thread rispetto al valore di concorrenza. La gestione del pool di thread non rientra nell'ambito di questo argomento, ma una buona regola generale consiste nell'avere almeno il doppio del doppio del numero di thread nel pool di thread in quanto sono presenti processori nel sistema. Per altre informazioni sul pool di thread, vedere pool di thread .

Funzioni di I/O supportate

È possibile usare le funzioni seguenti per avviare le operazioni di I/O completate usando le porte di completamento di I/O. È necessario passare la funzione a un'istanza della strutturaOVERLAPPED e a un handle di file associato in precedenza a una porta di completamento I/O (da una chiamata a CreateIoCompletionPort) per abilitare il meccanismo di porta di completamento I/O:

Informazioni su processi e thread

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus