Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 de varios procesadores. 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 asignado previamente que mediante la creación de subprocesos en el momento en que reciben una solicitud de E/S.
Cuándo usar puertos de finalización de E/S
| Scenario | Enfoque recomendado |
|---|---|
| Servidor de alto rendimiento que controla cientos o miles de conexiones simultáneas | Puertos de finalización de E/S , diseñados específicamente para esto. El núcleo gestiona la planificación de subprocesos para ajustarse al paralelismo de la CPU. |
| Simultaneidad moderada (decenas de operaciones asincrónicas) |
E/S del grupo de hilos (CreateThreadpoolIo) — API más sencilla, gestiona el IOCP internamente. Recomendados para código nuevo que no requiera control manual de subprocesos. |
| Operaciones simples de archivos asincrónicos en C++ moderno | Corrutinas de C++20 con un distribuidor IOCP personalizado, o .NET FileStream con async/await. |
| E/S de un solo hilo o de bajo volumen | E/S sincrónica o E/S superpuesta simple con señalización de eventos. IOCP agrega complejidad innecesaria para escenarios de flujo único. |
Nota:
API de grupos de subprocesos frente a IOCP puro: la API de grupos de subprocesos de Windows (CreateThreadpoolIo, StartThreadpoolIo) usa IOCP internamente, pero gestiona automáticamente el ciclo de vida de los subprocesos. Para las nuevas aplicaciones de servidor, considere primero la API de grupo de subprocesos: proporciona la misma escalabilidad con menos código repetitivo. Use IOCP directamente cuando necesite un control explícito sobre el nivel de simultaneidad del puerto de finalización o una gestión personalizada de subprocesos.
Cómo funcionan 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 asíncrona en uno de estos identificadores de archivo, se pone en cola un paquete de finalización de E/S en orden primero en entrar, primero en salir (FIFO) en 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, pueden ser desencolados en un orden diferente.
Nota:
El término identificador de archivo que 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 orden último en entrar, primero en salir (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 envía un paquete de finalización a un hilo, el sistema libera el último hilo (el más reciente) asociado a ese puerto, pasándole la información de finalización correspondiente a 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 al puerto de finalización de E/S especificado hasta que se produce una de estas 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 especificado. 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 hilos pueden utilizar 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 resulta útil, por ejemplo, para notificar eventos externos a los hilos de trabajo.
El identificador de puerto de finalización de E/S y todos los controladores de archivos 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 ya no existen más referencias al mismo. 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 cumplidas estas condiciones, una aplicación debe cerrar el identificador del puerto de finalización de E/S llamando 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 mismo identificador puede compartirse entre subprocesos del mismo proceso.
Subprocesos y concurrencia
La propiedad más importante de un puerto de finalización de E/S que se debe considerar detenidamente es el valor de concurrencia. El valor de simultaneidad de un puerto de finalización se especifica cuando se crea con CreateIoCompletionPort mediante el 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 con el puerto de finalización alcanza el valor de simultaneidad, el sistema bloquea la ejecución de cualquier subproceso posterior asociado a ese puerto de finalización hasta que el número de subprocesos ejecutables cae por debajo del valor de simultaneidad.
El escenario más eficiente se da 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 concurrencia. Consideremos lo que ocurre con un valor de concurrencia igual a uno y varios subprocesos en espera en la llamada a la función GetQueuedCompletionStatus. En este caso, si la cola siempre tiene paquetes de finalización en espera, 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án cambios de contexto de subproceso, porque el subproceso en ejecución recoge continuamente paquetes de finalización de tareas, impidiendo que los demás subprocesos puedan ejecutarse.
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 del 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 que espere 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 otros motivos, 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 al no permitir ningún nuevo subproceso activo hasta que el número de subprocesos activos se encuentra por debajo del valor de simultaneidad. Esta es una de las razones por las que la aplicación debe crear más subprocesos en su grupo de subprocesos que el valor de concurrencia. La administración del grupo de subprocesos está fuera del ámbito de este tema, pero una buena regla general es tener al menos el doble de subprocesos en el grupo de subprocesos que procesadores hay 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 a la función una instancia de la estructura OVERLAPPED y un identificador de archivo asociado previamente 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:
- AcceptEx
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- ReadFile
- TransactNamedPipe
- WaitCommEvent
- WriteFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- LPFN_WSARECVMSG (WSARecvMsg)
- WSARecv