Ports d’achèvement des E/S
Les ports d’achèvement des E/S fournissent un modèle de thread efficace pour le traitement de plusieurs requêtes d’E/S asynchrones sur un système multiprocesseur. Lorsqu’un processus crée un port d’achèvement d’E/S, le système crée un objet file d’attente associé pour les threads dont le seul objectif est de traiter ces requêtes. Les processus qui gèrent de nombreuses requêtes d’E/S asynchrones simultanées peuvent le faire plus rapidement et efficacement à l’aide de ports d’achèvement d’E/S conjointement avec un pool de threads pré-alloué que en créant des threads au moment où ils reçoivent une requête d’E/S.
Fonctionnement des ports d’achèvement des E/S
La fonction CreateIoCompletionPort crée un port d’achèvement d’E/S et associe un ou plusieurs handles de fichier à ce port. Lorsqu’une opération d’E/S asynchrone sur l’un de ces handles de fichiers se termine, un paquet d’E/S est mis en file d’attente dans le premier entré en premier sorti (FIFO) pour le port d’achèvement d’E/S associé. Une utilisation puissante pour ce mécanisme consiste à combiner le point de synchronisation pour plusieurs handles de fichiers en un seul objet, bien qu’il existe également d’autres applications utiles. Notez que pendant que les paquets sont mis en file d’attente dans l’ordre FIFO, ils peuvent être mis en file d’attente dans un ordre différent.
Note
Le terme handle de fichier comme utilisé ici fait référence à une abstraction système représentant un point de terminaison d’E/S superposé, pas seulement un fichier sur le disque. Par exemple, il peut s’agir d’un point de terminaison réseau, d’un socket TCP, d’un canal nommé ou d’un emplacement de messagerie. Tout objet système qui prend en charge les E/S superposées peut être utilisé. Pour obtenir la liste des fonctions d’E/S associées, consultez la fin de cette rubrique.
Lorsqu’un handle de fichier est associé à un port d’achèvement, le bloc d’état transmis n’est pas mis à jour tant que le paquet n’est pas supprimé du port d’achèvement. La seule exception est si l’opération d’origine retourne de façon synchrone avec une erreur. Un thread (créé par le thread principal ou le thread principal lui-même) utilise la fonction GetQueuedCompletionStatus pour attendre qu’un paquet d’achèvement soit mis en file d’attente vers le port d’achèvement d’E/S, plutôt que d’attendre directement la fin de l’E/S asynchrone. Les threads qui bloquent leur exécution sur un port d’achèvement d’E/S sont publiés dans l’ordre de dernière sortie (LIFO), et le paquet d’achèvement suivant est extrait de la file d’attente FIFO du port d’achèvement d’E/S pour ce thread. Cela signifie que, lorsqu’un paquet d’achèvement est libéré sur un thread, le système libère le dernier thread (le plus récent) associé à ce port, en lui transmettant les informations d’achèvement pour l’achèvement des E/S les plus anciennes.
Bien qu’un nombre quelconque de threads puisse appeler GetQueuedCompletionStatus pour un port d’achèvement d’E/S spécifié, lorsqu’un thread spécifié appelle GetQueuedCompletionStatus la première fois, il devient associé au port d’achèvement d’E/S spécifié jusqu’à ce qu’un des trois éléments se produise : Le thread se termine, spécifie un port d’achèvement d’E/S différent, ou ferme le port d’achèvement des E/S. En d’autres termes, un seul thread peut être associé à, au plus, un port d’achèvement d’E/S.
Lorsqu’un paquet d’achèvement est mis en file d’attente vers un port d’achèvement d’E/S, le système vérifie d’abord le nombre de threads associés à ce port en cours d’exécution. Si le nombre de threads en cours d’exécution est inférieur à la valeur d’accès concurrentiel (décrite dans la section suivante), l’un des threads en attente (le plus récent) est autorisé à traiter le paquet d’achèvement. Lorsqu’un thread en cours d’exécution termine son traitement, il appelle généralement GetQueuedCompletionStatus à nouveau, à quel moment il retourne avec le paquet d’achèvement suivant ou attend si la file d’attente est vide.
Les threads peuvent utiliser la fonction PostQueuedCompletionStatus pour placer les paquets d’achèvement dans la file d’attente d’un port d’achèvement d’E/S. Ainsi, le port d’achèvement peut être utilisé pour recevoir des communications d’autres threads du processus, en plus de recevoir des paquets d’E/S à partir du système d’E/S. La fonction PostQueuedCompletionStatus permet à une application de mettre en file d’attente ses propres paquets d’achèvement à usage spécial vers le port d’achèvement d’E/S sans démarrer une opération d’E/S asynchrone. Cela est utile pour avertir les threads de travail des événements externes, par exemple.
Le handle de port d’achèvement des E/S et chaque handle de fichier associé à ce port d’achèvement d’E/S particulier est appelé références au port d’achèvement d’E/S. Le port d’achèvement des E/S est libéré lorsqu’il n’y a plus de références à celui-ci. Par conséquent, tous ces handles doivent être correctement fermés pour libérer le port d’achèvement des E/S et ses ressources système associées. Une fois ces conditions satisfaites, une application doit fermer le handle de port d’achèvement des E/S en appelant la fonction CloseHandle.
Note
Un port d’achèvement d’E/S est associé au processus qui l’a créé et n’est pas partagé entre les processus. Toutefois, un handle unique peut être partagé entre les threads du même processus.
Threads et concurrence
La propriété la plus importante d’un port d’achèvement d’E/S à prendre en compte avec soin est la valeur d’accès concurrentiel. La valeur d’accès concurrentiel d’un port d’achèvement est spécifiée lors de sa création avec CreateIoCompletionPort via le paramètre NumberOfConcurrentThreads. Cette valeur limite le nombre de threads exécutables associés au port d’achèvement. Lorsque le nombre total de threads exécutables associés au port d’achèvement atteint la valeur d’accès concurrentiel, le système bloque l’exécution de tous les threads suivants associés à ce port d’achèvement jusqu’à ce que le nombre de threads exécutables tombe sous la valeur d’accès concurrentiel.
Le scénario le plus efficace se produit lorsqu’il existe des paquets d’achèvement en attente dans la file d’attente, mais aucune attente ne peut être satisfaite, car le port a atteint sa limite d’accès concurrentiel. Considérez ce qui se passe avec une valeur d’accès concurrentiel d’un et de plusieurs threads en attente dans l’appel de fonction GetQueuedCompletionStatus. Dans ce cas, si la file d’attente a toujours des paquets d’achèvement en attente, lorsque le thread en cours d’exécution appelle GetQueuedCompletionStatus, il ne bloque pas l’exécution car, comme mentionné précédemment, la file d’attente de threads est LIFO. Au lieu de cela, ce thread récupère immédiatement le paquet d’achèvement mis en file d’attente suivant. Aucun commutateur de contexte de thread ne se produit, car le thread en cours d’exécution récupère continuellement les paquets d’achèvement et les autres threads ne peuvent pas s’exécuter.
Note
Dans l’exemple précédent, les threads supplémentaires semblent inutiles et ne s’exécutent jamais, mais suppose que le thread en cours d’exécution n’est jamais mis dans un état d’attente par un autre mécanisme, se termine ou ferme son port d’achèvement d’E/S associé. Considérez toutes ces ramifications d’exécution de thread lors de la conception de l’application.
La meilleure valeur maximale globale à choisir pour la valeur de concurrence est le nombre de processeurs sur l’ordinateur. Si votre transaction a besoin d’un calcul long, une plus grande valeur d’accès concurrentiel permet d’exécuter davantage de threads. Chaque paquet d’achèvement peut prendre plus de temps, mais plus de paquets d’achèvement seront traités en même temps. Vous pouvez expérimenter la valeur de concurrence conjointement avec les outils de profilage pour obtenir le meilleur effet pour votre application.
Le système permet également à un thread en attente dans GetQueuedCompletionStatus de traiter un paquet d’achèvement si un autre thread en cours d’exécution associé au même port d’achèvement d’E/S entre dans un état d’attente pour d’autres raisons, par exemple la fonction SuspendThread. Lorsque le thread dans l’état d’attente commence à s’exécuter à nouveau, il peut y avoir une courte période lorsque le nombre de threads actifs dépasse la valeur de concurrence. Toutefois, le système réduit rapidement ce nombre en n’autorisant pas de nouveaux threads actifs tant que le nombre de threads actifs n’est pas inférieur à la valeur de concurrence. Il s’agit d’une raison pour laquelle votre application crée plus de threads dans son pool de threads que la valeur d’accès concurrentiel. La gestion des pools de threads dépasse l’étendue de cette rubrique, mais une bonne règle de pouce consiste à avoir au moins deux fois plus de threads dans le pool de threads qu’il existe des processeurs sur le système. Pour plus d’informations sur le regroupement de threads, consultez pools de threads.
Fonctions d’E/S prises en charge
Les fonctions suivantes peuvent être utilisées pour démarrer des opérations d’E/S qui se terminent à l’aide de ports d’achèvement d’E/S. Vous devez transmettre la fonction à une instance de la structureSE CHEVAUCHER et à un handle de fichier précédemment associé à un port d’achèvement d’E/S (par un appel à CreateIoCompletionPort) pour activer le mécanisme de port d’achèvement d’E/S :
- AcceptEx
- ConnectNamedPipe
- DeviceIoControl
- LockFileEx
- ReadDirectoryChangesW
- readFile
- TransactNamedPipe
- WaitCommEvent
- writeFile
- WSASendMsg
- WSASendTo
- WSASend
- WSARecvFrom
- LPFN_WSARECVMSG (WSARecvMsg)
- WSARecv
Rubriques connexes