Puertos de finalización de E/S

Los puertos de finalización de E/S proporcionan un modelo de subproceso eficaz para procesar varias solicitudes de E/S asincrónicas en un sistema multiprocesador. Cuando un proceso crea un puerto de finalización de E/S, el sistema crea un objeto de cola asociado para subprocesos cuyo único propósito es atender estas solicitudes. Los procesos que controlan muchas solicitudes de E/S asincrónicas simultáneas pueden hacerlo de forma más rápida y eficaz mediante el uso de puertos de finalización de E/S junto con un grupo de subprocesos asignados previamente que al crear subprocesos en el momento en que reciben una solicitud de E/S.

Funcionamiento de los puertos de finalización de E/S

La función CreateIoCompletionPort crea un puerto de finalización de E/S y asocia uno o varios identificadores de archivo a ese puerto. Cuando se completa una operación de E/S asincrónica en uno de estos identificadores de archivo, se pone en cola un paquete de finalización de E/S en primer lugar (FIFO) para el puerto de finalización de E/S asociado. Un uso eficaz para este mecanismo es combinar el punto de sincronización para varios identificadores de archivo en un solo objeto, aunque también hay otras aplicaciones útiles. Tenga en cuenta que, aunque los paquetes se ponen en cola en el orden FIFO, se pueden poner en cola en un orden diferente.

Nota

El término identificador de archivo como se usa aquí hace referencia a una abstracción del sistema que representa un punto de conexión de E/S superpuesto, no solo un archivo en el disco. Por ejemplo, puede ser un punto de conexión de red, un socket TCP, una canalización con nombre o una ranura de correo. Se puede usar cualquier objeto del sistema que admita E/S superpuesta. Para obtener una lista de las funciones de E/S relacionadas, consulte el final de este tema.

 

Cuando un identificador de archivo está asociado a un puerto de finalización, el bloque de estado pasado no se actualizará hasta que el paquete se quite del puerto de finalización. La única excepción es si la operación original devuelve de forma sincrónica un error. Un subproceso (ya sea uno creado por el subproceso principal o el propio subproceso principal) usa la función GetQueuedCompletionStatus para esperar a que se ponga en cola un paquete de finalización al puerto de finalización de E/S, en lugar de esperar directamente a que se complete la E/S asincrónica. Los subprocesos que bloquean su ejecución en un puerto de finalización de E/S se liberan en el último orden de salida (LIFO) y el siguiente paquete de finalización se extrae de la cola FIFO del puerto de finalización de E/S para ese subproceso. Esto significa que, cuando se libera un paquete de finalización en un subproceso, el sistema libera el último subproceso (más reciente) asociado a ese puerto, pasando la información de finalización para la finalización de E/S más antigua.

Aunque cualquier número de subprocesos puede llamar a GetQueuedCompletionStatus para un puerto de finalización de E/S especificado, cuando un subproceso especificado llama a GetQueuedCompletionStatus la primera vez, se asocia con el puerto de finalización de E/S especificado hasta que se produce una de las tres cosas: el subproceso sale, especifica un puerto de finalización de E/S diferente o cierra el puerto de finalización de E/S. Es decir, un único subproceso se puede asociar, como máximo, a un puerto de finalización de E/S.

Cuando un paquete de finalización se pone en cola en un puerto de finalización de E/S, el sistema comprueba primero cuántos subprocesos asociados a ese puerto se están ejecutando. Si el número de subprocesos que se ejecutan es menor que el valor de simultaneidad (descrito en la sección siguiente), se permite que uno de los subprocesos en espera (el más reciente) procese el paquete de finalización. Cuando un subproceso en ejecución completa su procesamiento, normalmente llama a GetQueuedCompletionStatus de nuevo, en cuyo punto vuelve con el siguiente paquete de finalización o espera si la cola está vacía.

Los subprocesos pueden usar la función PostQueuedCompletionStatus para colocar paquetes de finalización en la cola de un puerto de finalización de E/S. Al hacerlo, el puerto de finalización se puede usar para recibir comunicaciones de otros subprocesos del proceso, además de recibir paquetes de finalización de E/S del sistema de E/S. La función PostQueuedCompletionStatus permite a una aplicación poner en cola sus propios paquetes de finalización de propósito especial al puerto de finalización de E/S sin iniciar una operación de E/S asincrónica. Esto es útil para notificar a los subprocesos de trabajo de eventos externos, por ejemplo.

El identificador de puerto de finalización de E/S y todos los identificadores de archivo asociados a ese puerto de finalización de E/S concreto se conocen como referencias al puerto de finalización de E/S. El puerto de finalización de E/S se libera cuando no hay más referencias a él. Por lo tanto, todos estos identificadores deben cerrarse correctamente para liberar el puerto de finalización de E/S y sus recursos del sistema asociados. Una vez que se cumplan estas condiciones, una aplicación debe cerrar el identificador de puerto de finalización de E/S mediante una llamada a la función CloseHandle .

Nota

Un puerto de finalización de E/S está asociado al proceso que lo creó y no se puede compartir entre procesos. Sin embargo, un único identificador se puede compartir entre subprocesos en el mismo proceso.

 

Subprocesos y simultaneidad

La propiedad más importante de un puerto de finalización de E/S que se debe tener en cuenta cuidadosamente es el valor de simultaneidad. El valor de simultaneidad de un puerto de finalización se especifica cuando se crea con CreateIoCompletionPort a través del parámetro NumberOfConcurrentThreads . Este valor limita el número de subprocesos ejecutables asociados al puerto de finalización. Cuando el número total de subprocesos ejecutables asociados al puerto de finalización alcanza el valor de simultaneidad, el sistema bloquea la ejecución de cualquier subproceso subsiguiente asociado a ese puerto de finalización hasta que el número de subprocesos ejecutables caiga por debajo del valor de simultaneidad.

El escenario más eficaz se produce cuando hay paquetes de finalización en espera en la cola, pero no se puede satisfacer ninguna espera porque el puerto ha alcanzado su límite de simultaneidad. Tenga en cuenta lo que sucede con un valor de simultaneidad de uno y varios subprocesos esperando en la llamada de función GetQueuedCompletionStatus . En este caso, si la cola siempre tiene paquetes de finalización esperando, cuando el subproceso en ejecución llama a GetQueuedCompletionStatus, no bloqueará la ejecución porque, como se mencionó anteriormente, la cola de subprocesos es LIFO. En su lugar, este subproceso recogerá inmediatamente el siguiente paquete de finalización en cola. No se producirá ningún modificador de contexto de subproceso, ya que el subproceso en ejecución recoge continuamente paquetes de finalización y los demás subprocesos no se pueden ejecutar.

Nota

En el ejemplo anterior, los subprocesos adicionales parecen ser inútiles y nunca se ejecutan, pero eso supone que el subproceso en ejecución nunca se pone en un estado de espera por algún otro mecanismo, finaliza o cierra su puerto de finalización de E/S asociado. Tenga en cuenta todas estas ramificaciones de ejecución de subprocesos al diseñar la aplicación.

 

El mejor valor máximo general que se debe elegir para el valor de simultaneidad es el número de CPU en el equipo. Si la transacción requiere un cálculo largo, un valor de simultaneidad mayor permitirá que se ejecuten más subprocesos. Cada paquete de finalización puede tardar más tiempo en finalizar, pero se procesarán más paquetes de finalización al mismo tiempo. Puede experimentar con el valor de simultaneidad junto con las herramientas de generación de perfiles para lograr el mejor efecto para la aplicación.

El sistema también permite que un subproceso en espera en GetQueuedCompletionStatus procese un paquete de finalización si otro subproceso en ejecución asociado al mismo puerto de finalización de E/S entra en un estado de espera por otras razones, por ejemplo, la función SuspendThread . Cuando el subproceso del estado de espera comienza a ejecutarse de nuevo, puede haber un breve período cuando el número de subprocesos activos supere el valor de simultaneidad. Sin embargo, el sistema reduce rápidamente este número sin permitir nuevos subprocesos activos hasta que el número de subprocesos activos se encuentre por debajo del valor de simultaneidad. Este es un motivo para que la aplicación cree más subprocesos en su grupo de subprocesos que el valor de simultaneidad. La administración del grupo de subprocesos está fuera del ámbito de este tema, pero una buena regla general es tener un mínimo de dos veces más subprocesos en el grupo de subprocesos, ya que hay procesadores en el sistema. Para más información sobre la agrupación de subprocesos, consulte Grupos de subprocesos.

Funciones de E/S compatibles

Las siguientes funciones se pueden usar para iniciar operaciones de E/S que se completan mediante puertos de finalización de E/S. Debe pasar la función una instancia de la estructura SUPERPUESTA y un identificador de archivo previamente asociado a un puerto de finalización de E/S (mediante una llamada a CreateIoCompletionPort) para habilitar el mecanismo de puerto de finalización de E/S:

Acerca de los procesos y los subprocesos

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus