C / epoll() / Setup und Skalierung

Genius

Mitglied
Hallo Community,

ich steige jetzt von select() auf epoll() um, habe hierzu jedoch noch einige Fragen:

1. Macht es Sinn mehrere epoll Filedescriptoren anzulegen (z.B. für je 1000 Verbindungen), um den Server schneller zu machen?

2. Wenn ich mehrere Threads mit epoll_wait() laufen habe die auf den selben fd horchen, springt dann immer nur ein epoll_wait() an, wenn neue Daten (zum lesen) anstehen?
Oder genügt es um ein aufwachen mehrerer epoll_wait() zu verhindern EPOLLONESHOT zu setzen und nach dem verarbeiten der Daten (recv();) mit EPOLL_CTL_MOD erneut zu setzen und somit für weitere Zugriffe freizugeben?

3. Muss ich neben EPOLLONESHOT auch EPOLLIN setzen, damit epoll_wait(); aufwacht, wenn neue Daten zum Empfangen verfügbar sind?

4. Was passiert, wenn EPOLLONESHOT gesetzt ist und der Client neue Daten sendet, während ich alte Daten noch verarbeite? Löst epoll aus wenn ich mit EPOLL_CTL_MOD die Flags neu setzte und bereits Daten eingegangen sind?

5. Macht es Sinn aus dem Thread der epoll_wait() ausführt zur tatsächlichen Datenverarbeitung (send(); recv();) einen weiteren Thread zu starten?

6. Was ist gemeint mir: "EPOLLPRI - There is urgent out-of-band data available to read"

Mit freundlichen Grüßen und vielen Dank!
Genius
 
Hallo,

hast du ein konkretes Beispiel oder fragst du ganz allgemein? Falls nicht schau mal hier:
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
.. ich denke in dem Link sind einige deiner Fragen beantwortet.

Zu epolloneshot:
http://stackoverflow.com/questions/5541054/how-to-correctly-read-data-when-using-epoll-wait

Zu EPOLLPRI:
http://stackoverflow.com/questions/2555032/what-is-urgent-data

5. Macht es Sinn aus dem Thread der epoll_wait() ausführt zur tatsächlichen Datenverarbeitung (send(); recv();) einen weiteren Thread zu starten?
Wenn die Verarbeitung eines einzelnen Requests bzw. Datenpakets länger dauert, spricht IMHO nichts dagegen dafür einen neuen Thread zu spawnen (bzw. einen Thread aus einem Thread-Pool zu verwenden). Ob ein neuer Thread gespawned werden muss kann man von der Art der Anfrage abhängig machen - einfache -> schnelle Anfragen können direkt ausgeführt werden, komplexere Anfragen könnten in einer WorkQueue abgelegt und von einem Thread aus dem Thread-Pool abgearbeitet werden.
Du kannst dann im "Main-Thread" (mit der Event-Loop) nach dem Spawnen des Verarbeitungs-Threads einen Marker (generierte Id -> Future, Handle auf das Zukünftige Ergebnis)
auf das Ergebnis an den Client mittels write zurücksenden.

Dadurch blockierst du nicht die Event Loop - so können auch andere, schnellere / kleinere Requests flotter abgearbeitet werden. Deine Processing Threads kannst du dann wie schon erwähnt über einen (begrenzten) Thread-Pool verwalten -> Also (weit) weniger Threads als Connections denn genau das möchte man ja mit einer solchen Non-Blocking Event Verarbeitung erreichen ;-)

Gruß Tom
 
Schon mal vielen Dank!

2. -> erledigt
3. -> erledigt
4. -> erledigt
5. -> erledigt
6. -> erledigt

Neue Fragen:
7. Ist ein Zugriff verschiedener Threads mittels epoll_ctl() auf ein und den selben fd Thread-Safe? Oder muss ich locken?

8. Löst ein blockendes epoll_wait() aus, wenn fd nach dem Funktionsaufruf verändert wird und es neue Daten auf einer der neu hinzugefügten Sockets gibt? Oder muss epoll_wait() innerhalb einer while-Schleife ausgeführt werden und das timeout gesetzt werden, damit fd regelmäßig aktualisiert wird?

Danke!
Genius
 
Es kommt ganz auf den Verwendungszweck an. Wenn du die Arbeiten direkt im epoll-Thread durchführen willst, dann vielleicht, aber man muss bedenken, dass die Sockets beim einen FD viel mehr Gesamtlast haben könnten als die Sockets an einem anderen FD. Eine dynamische Verteilung wäre dann also sinnvoll. Im Anbetracht dessen macht das aber eher mehr Aufwand und resultiert unter Umständen sogar in einer schlechteren Performance.

Ich mache es eigentlich immer so, dass ich einen Thread als "Producer" habe, welcher die ganzen Events annimmt und Vorarbeit leistet (Mini-Befehle sofort verarbeiten, wie z.B. der Pseudo-PING) und den Rest an einen Thread-Pool gibt (die "Consumer").
Weitere Ansätze sind ja in den hier bereits verlinkten Seiten vermerkt.
 
Zuletzt bearbeitet:
Prima danke - dann sind meine theoretischen Überlegungen richtig gewesen :)!

Mit freundlichen grüßen
Genius
 
Einen wichtigen Hinweis noch zu Edge-Triggered: Beachtet das häufig erwähnte Problem von "EPOLLET and starvation". Versuche NIE einen Socket bis zum Auftreten von EAGAIN bzw. EWOULDBLOCK zu lesen, da das der perfekte Ansatz für eine DDoS-Attacke ist. Wenn der Client schneller Daten produzieren kann, als dein Server sie ließt, dann ist der Server durchgehend mit dem einen Client beschäftigt.
Ich habe es mit zwei verschiedenen Ansätzen gelöst (unterschiedliche Anwendungen natürlich^^): Eine gewisse Anzahl an Daten wird direkt gelesen (z.B. max 5 read() in Folge) oder die Daten nur soweit lesen, bis ein logisches Paket fertig ist. Nach der Verarbeitung werden die nächsten Daten gelesen, etc. So kann sichergestellt werden, das immer alle Clients mal dran sind. Natürlich muss man sich im Hintergrund merken, dass der Socket das entsprechende Event noch aktiv hat.
 
Zurück