poll(), epoll() etc.
-
Hi!
Danke für deine Antwort!
das mit der pipe ist unnötig. wenn du dein programm beenden willst, entfernst du einfach alle file descriptoren aus dem epoll, beendet diese und dann den epoll file descriptor.
Vom Prinzip her ist das fast, was ich will - mir gehts ja darum, den laufenden Poll zu wecken. Aber ich will dadurch einen oder mehrere zusätzliche File Deskriptoren in den laufenden Poll packen, ohne das gesamte FD-Set neu reintun zu müssen - nach dem Motto: neue File Deskriptoren rein und an alter Stelle weitergepollt... Da fällt mir, wie TNT schrieb, auch nur die Pipe ein, damit der Poll ein Ereignis bekommt und geweckt wird.
Viele Grüße, Stefan
-
du hast kein fdset bei epoll. jeder file descriptor liegt unabhängig der anderen im epoll drin. du kannst file descriptoren hinzufügen, entfernen oder modifizieren, während sie im epoll sind bzw. während ein thread bei epoll_wait wartet.
-
besserwisser schrieb:
du hast kein fdset bei epoll. jeder file descriptor liegt unabhängig der anderen im epoll drin. du kannst file descriptoren hinzufügen, entfernen oder modifizieren, während sie im epoll sind bzw. während ein thread bei epoll_wait wartet.
Kann man tatsächlich Dateideskriptoren hinzufügen oder entfernen während ein thread im epoll_wait ist? Ist das so dokumentiert? Ich habe nichts darüber gefunden.
-
Hallo nochmal!
Also einmal interessiert mich natürlich auch, ob man File Deskriptoren während des epoll_wait hinzufügen oder entfernen kann.
Dann:
besserwisser schrieb:
handler threads:
warten bis mindestens eine verwaltungsinformation in der queue ist und diese entfernen.
verbindung zu dieser verwaltungsinformation bearbeiten (lesen, schreiben, daten erzeugen, ...).
file descriptor wieder zu ce hinzufügen (level triggered, oneshot).Das bedeutet, man kann zum Schreiben und Lesen selbst zusätzliche Threads benutzen? Wenn das Schreiben und Lesen selbst ca. 30 Millisekunden dauert (bei größeren Daten entsprechend mehr), dann hätte man, würde man das Schreiben/Lesen im Poll-Thread miterledigen, ab einer bestimmten Menge eine spürbare Verzögerung des gesamten Servers. Wenn Threads in der Summe das Skript aber eher langsamer machen, kann man durch das Benutzen von Threads dann dieses Problem trotzdem lösen bzw. aufheben?
Viele Grüße, Marc
-
Marc21Ja schrieb:
Das bedeutet, man kann zum Schreiben und Lesen selbst zusätzliche Threads benutzen? Wenn das Schreiben und Lesen selbst ca. 30 Millisekunden dauert (bei größeren Daten entsprechend mehr), dann hätte man, würde man das Schreiben/Lesen im Poll-Thread miterledigen, ab einer bestimmten Menge eine spürbare Verzögerung des gesamten Servers. Wenn Threads in der Summe das Skript aber eher langsamer machen, kann man durch das Benutzen von Threads dann dieses Problem trotzdem lösen bzw. aufheben?
Nein! Darum geht es bei Threads nicht. Wie ich schon geschrieben habe, deine System-Calls dürfen nicht blockieren. Dann hast du auch keine Probleme. Größere Daten brauchen nicht länger zum senden, da du immer nur so viel in den Puffer schreibst, wie gerade gesendet werden kann, ohne das System zu blockieren. Also brauchst du um große Daten zu versenden mehrere Write-Events!
Threads benutzt du nur, damit du mehr Prozessor-Systeme ausnutzen kannst. D.h., wenn du so viele Verbindungen hast, dass deine Rechenleistung insgesamt nicht mehr ausreicht, kannst du Threads einsetzen und in dein System mehrere Prozessoren einbauen. Dann skaliert deine Anwendung auf mehrere Prozessoren.
-
tntnet schrieb:
Kann man tatsächlich Dateideskriptoren hinzufügen oder entfernen während ein thread im epoll_wait ist? Ist das so dokumentiert? Ich habe nichts darüber gefunden.
ich habe es selbst ausprobiert. man kann file descriptoren entfernen und hinzufügen. es steht in der doku aber leider nicht explizit drin. es gibt auch ein kleines problem dabei: epoll_wait wartet auch auf 0 file descriptoren. dh, es bleibt stecken, wenn man alle descriptoren entfernt hat.
beim close des epoll habe ich mich getäuscht. epoll_wait wird nicht aufgeweckt, wenn man den epoll file desciptor schließt. ein signal könnte hier abhilfe schaffen.
-
ProgChild schrieb:
Größere Daten brauchen nicht länger zum senden, da du immer nur so viel in den Puffer schreibst, wie gerade gesendet werden kann, ohne das System zu blockieren. Also brauchst du um große Daten zu versenden mehrere Write-Events!
Hi!
Die Daten, mit denen ich das getestet habe, waren ca. 100 Bytes bis 20 KB groß. Die Sockets sind nichtblockierend und du hast eine Lese-/Sendezeit von ca. 7-25 Millisekunden, ich habs nachgemessen. Die Größe scheint eine Rolle dabei zu spielen, wobei der Puffer bei 20 KB noch nicht voll sein sollte. Natürlich ist die Bearbeitungszeit der HTTP-Requests und der Erstellung des HTML-/Javascript-Codes noch aufwendiger als das Übertragen der Daten, aber wenn du jetzt z.B. eine auszugebende HTML-Seite mit dem Code und vielen Bildgrafiken hast, müsste sich das bei vielen Verbindungen und Lese-/Schreibvorgängen bereits in spürbaren Verzögerungen auswirken.
Viele Grüße, Marc
-
Die Sockets sind nichtblockierend und du hast eine Lese-/Sendezeit von ca. 7-25 Millisekunden, ich habs nachgemessen.
Dann hast du einen extrem langsamen Computer oder du hast schlecht gemessen.
-
besserwisser schrieb:
tntnet schrieb:
Kann man tatsächlich Dateideskriptoren hinzufügen oder entfernen während ein thread im epoll_wait ist? Ist das so dokumentiert? Ich habe nichts darüber gefunden.
ich habe es selbst ausprobiert. man kann file descriptoren entfernen und hinzufügen. es steht in der doku aber leider nicht explizit drin. es gibt auch ein kleines problem dabei: epoll_wait wartet auch auf 0 file descriptoren. dh, es bleibt stecken, wenn man alle descriptoren entfernt hat.
beim close des epoll habe ich mich getäuscht. epoll_wait wird nicht aufgeweckt, wenn man den epoll file desciptor schließt. ein signal könnte hier abhilfe schaffen.
Ausprobieren ist uninteressant. Wenn es nicht dokumentiert ist, kann man sich nicht darauf verlassen.
Mit welcher Kernel-Version hast Du ausprobiert? Hast Du auch den Kernel ausprobiert, der nächstes Jahr released wird? Die Kernelentwickler könnten den Code überarbeiten und das nicht dokumentierte Verhalten ändern.
-
@tntnet
dann verwende es halt nicht. ist ja deine entscheidung. ich habe das bis jetzt noch nicht gebraucht, werde es aber sicher verwenden, wenn ich es brauche. jeder ist da halt anders.dnotify wurde durch inotify ersetzt. deiner logik nach dürfte man damit keine api verwenden, da es auch hier passieren kann, dass sie wieder in einem nächsten release verschwindet. das könnte natürlich auch mit epoll passieren. was sagst du jetzt? ganz auf epoll verzichten?
etwas zu programmieren bedeutet, sich auf sich ändernde externe bedingungen einstellen und anpassen zu müssen. das wird dir noch oft passieren, da es massenhaft wichtige externe projekte gibt, deren entwickler von release managment und api stabilität keinen plan haben (z.b. openssl).
sich auf etwas nicht explizit dokumentiertest einzulassen ist also gleichwertig damit, sich auf eine api überhaupt einzulassen. deshalb sehe ich hier kein problem.
man kann die fehlende dokumentierung auch so sehen, dass eben das, was dokumentiert ist (man kann file descriptoren hinzufügen und entfernen) eben bedingungslos gilt. muss eine dokumentation auch alle bedingungen angeben, die NICHT erfüllt sein müssen?
-
Hallo!
besserwisser schrieb:
wichtig ist, dass du zu jedem file descriptor noch eine kleine zusatzinformation angeben kannst. meistens gibst du einen pointer auf die verwaltungsstruktur, die zu diesem file descriptor passt, an. sobald sich am filedescriptor was ändert, bekommst du diesen pointer vom kernel zurückgeliefert. damit kannst du sehr bequem gleich auf alle verwaltungsinfos zu diesem file descriptor zugreifen.
Also okay - ich habe eine Liste mit den Tasks, jeder Task ist eine Struktur vom Typ "xyztask". Wenn der Kernel mir einen Zeiger auf die jeweilige Aufgabe gibt, kann ich nach Abarbeitung den Task bequem löschen, ohne die ganze Liste durchzulaufen. Aber wie gebe ich den Pointer praktisch im Event an? Bei der Zuweisung ev.data.ptr=neuertask bekomme ich immer die Fehlermeldung, dass ptr kein member vom Struct epoll_event sei. Müsste ich nicht dabei auch irgendwie den Typ ("xyztask*") mit angeben?
Dann muss ich ja als Zweites noch die Anzahl der maxevents angeben. Da ich ein Array mit den Events brauche, vermute ich, dass wenn ich einen größeren Wert nehme, der Kernel länger braucht, um die Events zu verwalten oder? Zumindest verbraucht das Skript bei vielen größeren Arrays auch mehr Speicher. Sollte man das als dynamisches Array machen? Und wenn ja, gibt es einen einfachen und sicheren Anhaltspunkt, wie groß maxevents jeweils sein muss?
Vielen Dank schonmal + viele Grüße,
Marc
-
zum einen ist es kein script, was du machst sondern ein programm. der unterschied ist, dass ein script interpretiert wird und nicht in nativen (oder byte) code umgewandelt wird.
zum anderen hat der compiler recht, dass ptr kein attribute von epoll_event ist. den typ musst du nicht angeben, da jeder pointer auf daten automatisch auch ein pointer auf void ist und ptr eben genau dsa ist. casten deshalb unnötig. bitte um mehr code, damit ich mir das problem genauer ansehen kann.
bei jeder rückkehr von epoll_wait wirst du 0 bis maxevents bekommen. diese werden an die stelle von "events", dem zweiten parameter, geschrieben. da du dort natürlich speicher vorbereitet haben musst, musst du per maxevents dem kernel mitteilen, wieviel speicher du dort hast. es ist also die maximale anzahl an events, die ein aufruf von epoll_event zurückgibt. vielleicht ist 16 hier eine gute zahl. oder auch 128. ich weiß es nicht. ich denke aber, dass das abhängig von der auslastung und art des servers ist. du musst sehen, welcher wert günstig ist. ich verwende immer 16.
der kernel wartet nicht ewig, bis er maxevents bekommen hat. dh, er wird dir meistens weniger als maxevents events zurückliefern. solange dein server weit unter maxevents events pro wait aufruf bleibt, kannst du das beibehalten. man sollte solche feinheiten nur tunen, wenn es wirklich nötig ist.
dynamisch musst du das array nur machen, wenn du es zur laufzeit ändern willst. wenn du das nicht willst, ist das array auch problemlos am stack möglich.
-
Hi!
Danke für deine Antwort!
besserwisser schrieb:
zum anderen hat der compiler recht, dass ptr kein attribute von epoll_event ist. den typ musst du nicht angeben, da jeder pointer auf daten automatisch auch ein pointer auf void ist und ptr eben genau dsa ist. casten deshalb unnötig. bitte um mehr code, damit ich mir das problem genauer ansehen kann.
Ich hab gemerkt, der Fehler lag bei mir - ich habe nicht beachtet, dass events.data eine Union ist und hatte data.fd und data.ptr zugewiesen. Bei der nächsten Zuweisung ist er dann offenbar davon ausgegangen, dass events.data vom Typ Integer (fd) ist. Jetzt macht er's korrekt, wie du es oben beschrieben hast.
besserwisser schrieb:
es ist also die maximale anzahl an events, die ein aufruf von epoll_event zurückgibt. vielleicht ist 16 hier eine gute zahl. oder auch 128. ich weiß es nicht. ich denke aber, dass das abhängig von der auslastung und art des servers ist. du musst sehen, welcher wert günstig ist. ich verwende immer 16.
Das hört sich gut an
. Ich weiß, dass ein Chat selten so voll ist, aber wenn man schon sowas programmiert: Nehmen wir mal an, man hätte zu einem bestimmten Zeitpunkt eine sehr hohe Auslastung mit mehreren tausend Verbindungen (manche Routinen müssen die alle gleichzeitig bedienen), bedeutet das, dass wenn mehr als 16 oder 128 gleichzeitige Ereignisse stattfinden, der Kernel dann 16/128 Ereignisse ausgibt und beim nächsten Aufruf von epoll_wait merkt, es sind noch nicht alle Ereignisse ausgegeben und sofort zurück kehrt, um mit jeder Rückkehr 16/128 der restlichen Ereignisse zu liefern?
Noch eine kleine Frage: Wenn ich einen fd schließe, muss ich nicht EPOLL_CTL_DEL aufrufen, sondern epoll entfernt den fd aus den Events automatisch, wenn ich den Socket mit close(c) z.B. aus einem anderen Thread schließe oder?
Viele Grüße + Danke,
Marc
-
besserwisser schrieb:
der kernel wartet nicht ewig, bis er maxevents bekommen hat. dh, er wird dir meistens weniger als maxevents events zurückliefern. solange dein server weit unter maxevents events pro wait aufruf bleibt, kannst du das beibehalten. man sollte solche feinheiten nur tunen, wenn es wirklich nötig ist.
http://pl.atyp.us/content/tech/servers.html -- siehe Context Switches
Man könnte die Serverarchitektur auch so machen: Anstatt einen Listener-Thread und/oder einen weiteren Thread für Epoll und die Workerthreads zu machen, wird um epoll_wait ein Lock gesetzt. So kommt immer nur ein Thread ins Lock hinein. Epoll_wait lässt man die maximale Zahl an events ausgeben, die man im Thread bearbeiten will. Anschließend gibt man den Lock frei und bearbeitet die events, während ein weiterer Thread, der epoll_wait aufgerufen hat, die nächsten Aufgaben abholt. Sind in einer Aufgabe weitere Polls auszuführen, lässt man vom nächsten Thread, der in den Lock kommt, die Tasks in die Events adden. So vermeidet man, dass zu viele Aufgaben durch Locking zwischen den Threads hin- und herverschoben werden.
-
@Caipirinha
was du mir mit diesem link sagen willst, weiß ich leider nicht.
Caipirinha schrieb:
Epoll_wait lässt man die maximale Zahl an events ausgeben
das geht nicht. man kann das epoll_wait nicht machen "lassen", da es nach einer bestimmten zeit von selbst zurückkehrt. bedenke sonst das problem, dass sich maxevents clients zuerst verbinden müssen, bevor du den ersten behandelst.
sonst könnte ich nicht sehen, welchen vorteil deine methode hat. du hast etwas, was du lock nennst, ich eine semaphore. das ist das gleiche. das einzige, was du dir sparst, ist das einfügen in die warteschlange. das ist aber O(1) und selbst bei extrem ausgelasteten servern bedeutungslos.
Marc21Ja schrieb:
...bedeutet das, dass wenn mehr als 16 oder 128 gleichzeitige Ereignisse stattfinden, der Kernel dann 16/128 Ereignisse ausgibt und beim nächsten Aufruf von epoll_wait merkt, es sind noch nicht alle Ereignisse ausgegeben und sofort zurück kehrt, um mit jeder Rückkehr 16/128 der restlichen Ereignisse zu liefern?
so ist es.
Marc21Ja schrieb:
Noch eine kleine Frage: Wenn ich einen fd schließe, muss ich nicht EPOLL_CTL_DEL aufrufen, sondern epoll entfernt den fd aus den Events automatisch, wenn ich den Socket mit close(c) z.B. aus einem anderen Thread schließe oder?
auch hier: so ist es.
-
Moin,
Ich bin neu auf den Foren hier, wies ausschaut passt mein Problem(chen) hier rein.Ich hab ein Serverprogramm geschrieben, welches auf einem Ubuntu 8.04 (64bit) läuft. Selbiges benutzt epoll mit nichtblockierenden Sockets. Hinzugefügte Sockets werden konfiguriert mit der Eventmaske EPOLLIN | EPOLLET | EPOLLONESHOT :
epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
if (epoll_ctl(epfd,EPOLL_CTL_ADD,iSocket,&ev)<0);
{...
das funktioniert fehlerfrei.Die Daten werden dann aus dem Socket gelesen. Vor der Verarbeitung setze ich erneut eine Eventmaske (da die Reaktivierung aus einem anderen thread stattfindet) :
epoll_event ev;
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
if (epoll_ctl(epfd,EPOLL_CTL_MOD,iSocket,&ev)<0)
{...Hierbei gibt epoll_ctl -1 zurück. Die Fehlerbehandlung meldet mir Fehler 11 (strerror liefert : Resource temporarily not available). Speicher ist jedoch durchaus genug frei, die Fehlermeldung scheint also nicht eindeutig...
Gibt es Erfahrungen, welche Fehler ein solches Verhalten auslösen würden bzw. wie diese zu lösen sind ?EDIT :
Die Fehlerquelle war lediglich die Handhabung der Parameter für epoll_wait in meinem entsprechenden Workerthread. Das benötigt nämlich in der Übergabe einen Zeiger auf ein Array aus epoll_events festgelegter Größe, nicht aber ein stumpf definiertes epoll_event, was man leider in einigen Tutorials/Dokus findet. Dann funzt zwar die Ausgabe einzelner Events, aber leider kann es durchaus vorkommen daß der zurückgegebene Inhalt eben ein paar mikrosekunden später für den anderen thread ins Nirvana zeigt, und bei der nächsten Verwendung gehts halt in die Hose