Quelle est la différence entre epoll, poll, threadpool?

quelqu'un pourrait-il expliquer quelle est la différence entre epoll , poll et threadpool?

  • Quels sont les avantages et les inconvénients?
  • des suggestions pour les cadres?
  • des suggestions pour des tutoriels simples/de base?
  • il semble que epoll et poll soient spécifiques de Linux... Existe-t-il une alternative équivalente pour Windows?
73
demandé sur Cœur 2010-11-04 04:37:49

1 réponses

Threadpool ne rentre pas vraiment dans la même catégorie que poll et epoll, donc je suppose que vous faites référence à threadpool comme dans "threadpool to handle many connections with one thread per connection".

pour et contre

  • pool de threads
    • raisonnablement efficace pour la concurrence petite et moyenne, peut même dépasser d'autres techniques.
    • Makes l'utilisation de plusieurs cœurs.
    • ne va pas bien au-delà de" plusieurs centaines " même si certains systèmes (par exemple Linux) peuvent en principe programmer 100.000 s de threads très bien.
    • Naïve de la mise en œuvre des expositions " tonnerre troupeau problème".
    • mis à part le changement de contexte et le Thundering herd, il faut considérer la mémoire. Chaque fil a une pile (généralement au moins un mégaoctet). Un millier de fils donc prenez un gigabyte de RAM juste pour stack. Même si cette mémoire n'est pas engagée, elle enlève tout de même beaucoup d'espace d'adresse sous un OS 32 bits (pas vraiment un problème sous 64 bits).
    • Threads can actually use epoll , bien que la manière évidente (tous les threads bloquent sur epoll_wait ) est inutile, parce que epoll se réveillera chaque thread attente sur elle, donc il aura toujours les mêmes problèmes.
      • solution optimale: le thread simple écoute sur epoll, fait le multiplexage d'entrée, et les mains compléter les requêtes à un threadpool.
      • futex est votre ami ici, en combinaison avec par exemple une file d'attente avant rapide par fil. Bien que mal documenté et lourd, futex offre exactement ce qu'il faut. epoll peut retourner plusieurs événements à la fois, et futex vous permet efficacement et d'une manière contrôlée avec précision wake N a bloqué les threads à la fois (n étant min(num_cpu, num_events) idéalement), et dans le meilleur des cas, il n'implique pas un commutateur syscall/context supplémentaire du tout.
      • pas trivial à mettre en œuvre, prend quelques précautions.
  • fork (A. K. A. old fashion threadpool)
    • raisonnablement efficace pour la concurrence petite et moyenne.
    • Ne s'adapte pas bien au-delà de "quelques centaines".
    • les changements de Contexte sont beaucoup plus cher (les différents espaces d'adresse!).
    • Balance considérablement pire sur les systèmes plus anciens où fourche est beaucoup plus cher (copie profonde de toutes les pages). Même sur les systèmes modernes fork n'est pas" libre", bien que le plafond soit en grande partie coalescé par le mécanisme de copie-sur-écriture. Sur les grands ensembles de données qui sont également modifié , un nombre considérable de défauts de page après fork peuvent avoir un impact négatif sur la performance.
    • cependant, prouvé pour travailler de manière fiable pendant plus de 30 ans.
    • ridiculement facile à mettre en œuvre et solide comme un roc: si l'un des processus s'effondre, le monde ne s'arrête pas. Il n'y a (presque) rien que vous puissiez faire de mal.
    • très enclin à"troupeau tonnerre".
  • poll / select
    • deux saveurs (BSD vs. System V) de plus ou moins la même chose.
    • un peu vieux et lent, usage quelque peu maladroit, mais il n'y a pratiquement aucune plate-forme qui ne les supporte pas.
    • attend jusqu'à ce que" quelque chose arrive " sur un ensemble de descripteurs
      • permet à un thread/process de traiter plusieurs requêtes à la fois.
      • pas d'utilisation multi-core.
    • doit copier la liste des descripteurs de l'espace utilisateur à l'espace du noyau à chaque fois que vous attendez. Effectuer une recherche linéaire sur les descripteurs. Ce qui limite son efficacité.
    • ne se monte pas bien à" des milliers " (en fait, limite dure autour de 1024 sur la plupart des systèmes, ou aussi bas que 64 sur certains).
    • utilisez-le parce qu'il est portable si vous ne traitez qu'une douzaine de descripteurs de toute façon (aucun problème de performance là), ou si vous devez des plateformes de support qui n'ont rien de mieux. N'utilisez pas le contraire.
    • sur le plan conceptuel, un serveur devient un peu plus compliqué qu'un serveur Fourché, car vous devez maintenant maintenir de nombreuses connexions et une machine d'État pour chaque connexion, et vous devez Multiplexer entre les requêtes au fur et à mesure qu'elles apparaissent, assembler des requêtes partielles, etc. Un simple serveur bifurqué ne connaît qu'une seule socket (deux, en comptant la socket d'écoute), lit jusqu'à ce qu'il ait ce qu'il veut ou jusqu'à ce que la connexion est à moitié fermée, puis écrit ce qu'elle veut. Il ne s'inquiète pas de blocage ou de préparation ou de famine, ni de certaines données sans rapport, c'est le problème d'un autre processus.
  • epoll
    • Linux seulement.
    • Concept de modifications coûteuses et d'attente efficace:
      • Copie l'information sur les descripteurs à espace du noyau lorsque des descripteurs sont ajoutés ( epoll_ctl )
        • c'est habituellement quelque chose qui arrive rarement .
      • Ne pas besoin de copier des données dans l'espace du noyau lors de l'attente pour les événements ( epoll_wait )
        • c'est habituellement quelque chose qui arrive très souvent .
      • ajoute le serveur (ou plutôt sa structure epoll) aux files d'attente des descripteurs
        • le descripteur sait donc qui écoute et signale directement les serveurs le cas échéant plutôt que les serveurs cherchant une liste de descripteurs
        • à L'opposé de poll fonctionne
        • O (1) avec un petit k(très rapide) en ce qui concerne le nombre de descripteurs, au lieu de O (n)
    • fonctionne très bien avec timerfd et eventfd (avec une résolution et une précision étonnantes).
    • fonctionne bien avec signalfd , éliminant la manipulation maladroite des signaux, les faisant partie du flux normal de commande d'une manière très élégante.
    • une instance epoll peut héberger d'autres instances epoll de manière récursive
    • hypothèses faites par ce modèle de programmation:
      • la plupart des descripteurs sont inactifs la plupart du temps, peu de choses (par exemple "données reçues", "connexion fermée") se produisent réellement sur quelques descripteurs.
      • la plupart du temps, vous ne voulez pas ajouter/supprimer des descripteurs de l'ensemble.
      • la plupart du temps, vous attendez que quelque chose arrive.
    • quelques petits pièges:
      • une epoll à déclenchement de niveau réveille tous les threads qui l'attendent (ceci est " fonctionne comme prévu"), par conséquent, la manière naïve d'utiliser epoll avec un threadpool est inutile. Au moins pour un serveur TCP, ce n'est pas un gros problème puisque les requêtes partielles devraient être assemblées en premier de toute façon, donc une implémentation naïve multithreaded ne fera pas l'un ou l'autre sens.
      • ne fonctionne pas comme on pourrait s'y attendre avec le fichier read/writes ("toujours prêt").
      • ne pouvait pas être utilisé avec AIO jusqu'à récemment, maintenant possible via eventfd , mais nécessite une fonction (à ce jour) non documentée.
      • si les hypothèses ci-dessus sont et non vrai, epoll peut être inefficace, et poll peut fonctionner également ou mieux.
      • epoll ne peut pas faire "magie", c'est-à-dire qu'il est toujours nécessairement O(N) en ce qui concerne le nombre de événements qui se produisent .
      • cependant, epoll joue bien avec le nouveau recvmmsg syscall, puisqu'il renvoie plusieurs notifications de préparation à la fois (autant que disponibles, jusqu'à tout ce que vous spécifiez comme maxevents ). Cela permet par exemple de recevoir 15 notifications EPOLLIN avec un appel syscall sur un serveur occupé, et de lire les 15 messages correspondants avec un second appel syscall (une réduction de 93% des appels syscall!). Malheureusement, toutes les opérations sur un recvmmsg invokation se réfèrent à la même socket, donc il est surtout utile pour les services basés sur UDP (pour TCP, il devrait y avoir une sorte de recvmmsmsg syscall qui prend aussi une socket descripteur par article!).
      • les descripteurs devraient toujours être défini à non bloquant et on devrait vérifier pour EAGAIN même en utilisant epoll parce qu'il ya des situations exceptionnelles où epoll rapports État de préparation et une lecture (ou écrire) sera encore bloc. C'est également le cas pour poll / select sur certains noyaux (bien qu'il ait probablement été fixé).
      • avec a naïf mise en œuvre, la famine des expéditeurs lents est possible. En lisant aveuglément jusqu'à ce que EAGAIN soit retourné à la réception d'une notification, il est possible de lire indéfiniment de nouvelles données entrantes d'un expéditeur rapide tout en affamant complètement un expéditeur lent (tant que les données continuent à venir dans assez rapidement, vous pourriez ne pas voir EAGAIN pendant un certain temps!). S'applique à poll / select de la même manière.
      • mode déclenché par le bord a quelques bizarreries et un comportement inattendu dans certaines situations, puisque la documentation (à la fois les pages de manuel et TLPI) est vague ("probablement", "devrait", "pourrait") et parfois trompeuse sur son fonctionnement.

        La documentation indique que plusieurs threads attendant sur un epoll sont tous signalés. Il indique en outre qu'une notification vous indique si L'activité IO a eu lieu depuis le dernier appel à epoll_wait (ou depuis l'ouverture du descripteur, s'il n'y a pas eu précédemment appeler.)

        Le comportement vrai, observable dans le mode de bord-déclenché est beaucoup plus proche de "réveille le premier fil qui a appelé epoll_wait , signalant que l'activité D'IO s'est produite depuis n'importe qui dernier appelé soit "1519670920 epoll_wait ou une fonction de lecture/écriture sur le descripteur, et par la suite ne signale l'état de préparation à nouveau to the next thread calling or déjà bloqué dans epoll_wait , pour toute opération se produisant après anyone appelé a de la fonction lire (ou écrire) sur le descripteur". Ce genre de logique, aussi... ce n'est pas exactement ce que la documentation suggère.
  • kqueue
    • analogue BSD à epoll , usage différent, effet similaire.
    • fonctionne aussi sur Mac OS X
    • rumeur à être plus rapide (Je ne l'ai jamais utilisé, donc ne peut pas dire si c'est vrai).
    • enregistre les événements et renvoie un résultat défini dans un seul syscall.
  • IO ports de fin d'
    • Epoll pour Windows, ou plutôt epoll sur les stéroïdes.
    • fonctionne parfaitement avec tout qui est waitable ou modifiable d'une manière ou d'une autre (sockets, waitable timers, file operations, threads, processes)
    • si Microsoft a une bonne chose dans Windows, il est ports d'achèvement:
      • fonctionne sans souci hors de la boîte avec un nombre quelconque de fils
      • Pas de tonnerre troupeau
      • Réveille fils un par un dans un ordre LIFO
      • maintient les caches chaudes et minimise les commutateurs de contexte
      • respecte le nombre de processeurs sur la machine ou livre le nombre désiré de travailleurs
    • permet à l'application de poster des événements, ce qui se prête à une très facile, infaillible, et efficace mise en file d'attente de travail en parallèle (programmes de plus de 500.000 tâches par seconde sur mon système).
    • inconvénient mineur: ne supprime pas facilement les descripteurs de fichier une fois ajoutés (doit fermer et rouvrir).

Cadres

libevent -- la version 2.0 prend également en charge les ports d'achèvement sous Windows.

ASIO -- si vous utilisez Boost dans votre projet, ne cherchez pas plus loin: vous avez déjà cette disponible comme boost-asio.

des suggestions pour des tutoriels simples/de base?

les cadres énumérés ci-dessus viennent avec une documentation complète. Le Linux docs et MSDN explique epoll et les ports d'achèvement en détail.

Mini-tutoriel pour l'utilisation d'epoll:

int my_epoll = epoll_create(0);  // argument is ignored nowadays

epoll_event e;
e.fd = some_socket_fd; // this can in fact be anything you like

epoll_ctl(my_epoll, EPOLL_CTL_ADD, some_socket_fd, &e);

...
epoll_event evt[10]; // or whatever number
for(...)
    if((num = epoll_wait(my_epoll, evt, 10, -1)) > 0)
        do_something();

Mini-tutoriel pour les ports de complétion IO (note appelant CreateIoCompletionPort deux fois avec des paramètres différents):

HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); // equals epoll_create
CreateIoCompletionPort(mySocketHandle, iocp, 0, 0); // equals epoll_ctl(EPOLL_CTL_ADD)

OVERLAPPED o;
for(...)
    if(GetQueuedCompletionStatus(iocp, &number_bytes, &key, &o, INFINITE)) // equals epoll_wait()
        do_something();

( ces mini-touches omettent toute sorte de vérification d'erreur, et j'espère que je n'ai pas fait de fautes de frappe, mais ils devraient en général être d'accord pour vous donner une idée.)

EDIT:

Notez que les ports de completion (Windows) fonctionnent de manière conceptuelle dans l'autre sens comme epoll (ou kqueue). Ils signalent, comme leur nom l'indique, achèvement , pas préparation . C'est-à-dire que vous lancez une requête asynchrone et l'oubliez jusqu'à ce que quelque temps plus tard, vous êtes informé qu'il a terminé (soit avec succès ou pas donc, beaucoup de succès, et il y a le cas exceptionnel de "immédiatement").

Avec epoll, vous bloquez jusqu'à ce que vous soyez averti que "certaines données" (éventuellement aussi peu qu'un octet) sont arrivées et sont disponibles ou qu'il y a suffisamment d'espace tampon pour que vous puissiez effectuer une opération d'écriture sans bloquer. Seulement alors, vous commencez l'opération réelle, qui alors, espérons-le, ne bloquera pas (autre que ce que vous attendez, il n'y a pas de garantie stricte pour cela -- il est par conséquent, une bonne idée de définir des descripteurs à nonblocking et de vérifier pour EAGAIN [EAGAIN et EWOULDBLOCK pour les sockets, parce que Oh joy, la norme permet deux valeurs d'erreur différentes]).

207
répondu Damon 2013-03-02 16:18:25