Server: Threadverteilung
-
Ich erzeuge threads immer nur mit 4KB stack und habe bis jetzt noch nie einen overflow bekommen.
-
volkard schrieb:
die wichtige frage ist, ob du pro client einen thread nimmst. das kann bei komplexeren protokollen die programmierung stark vereinfachen und ist theoretisch(!) in keiner langsameren komplexitätsklasse als threadpooling.
Huh, da hab ich noch Bedenken, viele Verbindungen sind nur sehr kurzlebig; andererseits soll der Server nur unter Linux laufen, da sollen Threads ja recht leichtgewichtig sein. Mal sehen, da es eh nur eine Spielerei ist, nehme ich erstmal das (für mich) schwerere, Pooling.
hustbaer schrieb:
Option 2 hat den nachteil, dass sie schlecht damit klarkommt, wenn zufällig grad alle Verbindungen die von Thread X bedient werden gleichzeitig was wollen, und alle anderen nur rumhängen und nix tun.
Das ist ein gutes Argument, da muss ich mal drüber nachdenken.
hustbaer schrieb:
Für Large-Scale wird oft das "proactive pattern" verwendet, was entfernte Ähnlichkeiten zu deiner Option 1 hat. Wenn dich interessiert wie das aussieht hilft dir Google. [...] Oder guck dir an wie IO-Completion-Ports unter Windows funktionieren -- mit denen kann man auch "proactive" arbeiten.
So viel hab ich bei Google leider nicht gefunden. Das was ich gefunden hab, sah für mich so aus: Reactive=Ich reagiere auf z.B. per Callback eingehende Daten; Proactive=Ich sage, wieviele Daten ich brauche und mache nix, bis ich sie hab. Stimmt das so in etwa? Wenn ja, eignet sich für mich aber, glaube ich, der reaktive Ansatz weit mehr. Ich hab keine festen Nachrichtenlängen und das Protokoll ist Ping-Pong-mäßig, vor meiner Antwort gibt's auf der Leitung auch keine neue Anfrage.
Mit den IO-Completion-Ports hatte ich mich mal befasst, das Konzept hatte mich damals schon beeindruckt. Passt aber irgendwie nicht zu dem, was ich jetzt unter "proactive" verstanden hab.edit:
volkard schrieb:
war es nicht so, daß ein böser übeltäter bei nur einem listener den listener leicht ddosen kann und deinen server zu einem großnotstand führt?
Also machen mehrere Listener auf dem selben Port Sinn? Was wäre denn eine gute Anzahl?
-
volkard schrieb:
edit: wieviele threads kann man eigentlich in einer sekunde starten und löschen? hab ich nicht vor jahren schon 10000 pro sekunde gehabt? vielleicht ist das gar nicht mehr so wichtig.
Das kann man getrost vernachlässigen.
Mein Performance-Test stellt 100 Verbindungen zum Server her, schickt einen simplen Job (SQL-Statemnt: SELECT * FORM TABELLE_MIT_CA_200_DATENSAETZEN) und trennt die Verbindung wieder.
Anschliessend wird die benötigte Zeit durch 100 geteilt um die Dauer pro Verbindung auszurechnen: 2 Millisekunden pro Verbindung. Das ist via localhost auf einem Quadcore unter Windows Vista.
Bedeutet 2 Millisekunden zum Thread erstellen, Datenbank-Zugriff, Datenübertragung, Thread zerstören.
Da 95 % der echten Jobs 0.2 bis 0.5 Sekunden dauern, mache ich mir um die lächerliche Zeit zum Erstellen des Threads keinen Kopf. Ich optimiere nur da wo es Weh tut. Und hier spüre ich nichtmal ein Kitzeln...
-
volkard schrieb:
hustbaer schrieb:
Macht 3000 Threads = wahnsinns Overhead. Auf einer 32 Bit Maschine geht das nichtmal mehr, weil man die 3000 Stack-Frames nichtmal im Adressraum unterbekommt.
unterkommen geht. man muß ja nicht das voreingestellte 1M für den stack lassen und kann verfügen, daß rekursionen nur erlaubt sind, wenn sie logarithmisch beschränkt sind (oder wie damals die bundesbahn rekursionen ganz verbieten
) und speicherallokationen über 4k auf dem stack nicht sein dürfen.
threaderstellung und -zerstörung sind trotzdem ein doller overhead, dem will ich nicht widersprochen haben.edit: wieviele threads kann man eigentlich in einer sekunde starten und löschen? hab ich nicht vor jahren schon 10000 pro sekunde gehabt? vielleicht ist das gar nicht mehr so wichtig.
Was den Stack angeht: kann man unter Windows überhaupt weniger als die Default-Size anfordern? Ich weiss man kann das STACK_SIZE_PARAM_IS_A_RESERVATION Flag mitgeben, aber ich bin mir nicht so sicher ob Windows nicht trotzdem mindestens soviel verwendet wie im PE-Image angegeben ist. Natürlich kann man das Default im PE-Image ändern...
Was Threads/Sekunde angeht: man kann ja die Connection-Threads trotzdem poolen. Wenn eine Connection geschlossen wird steckt man den Thread einfach in den Pool zurück, und wenn eine neue Connection ankommt nimmt man einen aus dem Pool.
Was man IMO aber nicht vernachlässigen sollte sind andere Dinge wie z.B. Database Connections. Der Application-Server mag 10000 Threads noch leicht mitmachen, aber ob der DB-Server so glücklich ist wenn er 10000 DB-Connections gleichzeitig hat? Natürlich *kann* man auch mit ein Thread pro Connection weniger DB-Connections als Threads verwenden, aber was einstmal so einfach und schön war, wird dadurch auch gleich komplizierter.
-
volkard schrieb:
war es nicht so, daß ein böser übeltäter bei nur einem listener den listener leicht ddosen kann und deinen server zu einem großnotstand führt?
Inwieweit helfen da mehrere Listener-Threads?
Davon abgesehen ist DDOS sowieso etwas wogegen man sich nicht wirklich schützen kann. Man kann zwar ein System bauen dass nicht unter einer DDOS zusammenbricht, aber man kann soweit ich weiss nicht verhindern dass die Attacke die Erreichbarkeit des Systems (für neue Connections) zumindest gravierend verschlechtert.
-
Badestrand schrieb:
hustbaer schrieb:
Für Large-Scale wird oft das "proactive pattern" verwendet, was entfernte Ähnlichkeiten zu deiner Option 1 hat. Wenn dich interessiert wie das aussieht hilft dir Google. [...] Oder guck dir an wie IO-Completion-Ports unter Windows funktionieren -- mit denen kann man auch "proactive" arbeiten.
So viel hab ich bei Google leider nicht gefunden. Das was ich gefunden hab, sah für mich so aus: Reactive=Ich reagiere auf z.B. per Callback eingehende Daten; Proactive=Ich sage, wieviele Daten ich brauche und mache nix, bis ich sie hab. Stimmt das so in etwa? Wenn ja, eignet sich für mich aber, glaube ich, der reaktive Ansatz weit mehr. Ich hab keine festen Nachrichtenlängen und das Protokoll ist Ping-Pong-mäßig, vor meiner Antwort gibt's auf der Leitung auch keine neue Anfrage.
Mit den IO-Completion-Ports hatte ich mich mal befasst, das Konzept hatte mich damals schon beeindruckt. Passt aber irgendwie nicht zu dem, was ich jetzt unter "proactive" verstanden hab.Reactive ist "ruf Funktion XYZ auf, wenn auf Socket X Daten angekommen sind".
Die Funktion XYZ ist dann dafür zuständig die Daten zu lesen und zu verarbeiten.Proactive ist "lies 10 Bytes von Socket X in den Puffer Y, und ruf mir die Funktion XYZ auf, sobald die 10 Byte gelesen wurden". Wenn die Funktion XYZ aufgerufen wurde, stehen die 10 Byte bereits in Puffer Y.
Der grosse Unterschied ist: das proactive Pattern kann man leicht erweitern um z.B. aus dem "lies 10 Bytes" ein "lies ein Foo-Paket" zu machen. Wobei das "Foo-Paket" etwas ist was man selbst definieren kann, und auch nicht immer gleich gross sein muss. Siehe z.B. boost::asio::async_read_until:
http://www.boost.org/doc/libs/1_37_0/doc/html/boost_asio/reference/async_read_until.htmlNatürlich lässt sich Proactive auch über eine reaktive API implementieren. Boost.ASIO macht das z.B. auch, wenn's auf dem jeweiligen System keine besser geeignete API gibt.
-
Badestrand schrieb:
...
das design hat nicht viel mit meinung und tendieren zu tun, es haengt vom anwendungsfall ab und was das ziel des ganzen ist.
dass es "gut" laufen soll ist klar, aber wie du 'gut' definierst ist dann entscheident.wenn du immer 10 verbindungen pro sekunde hast und diese lange zum abarbeiten brauchen, nimmst du wohl ein anderes system als wenn du 10000 verbindungen hast.
hast du konstant 10000 verbindungen von denen jede ab und zu was machen will, kannst du ebenfalls anders damit arbeiten als mit 16 unter hochlast.
hast du x unabhaengige verbindungen, ist die performance skalierung vermutlich auch anders als wenn sie eine knapper resource sich teilen muessen.
das haengt am ende auch von der umgebung ab, wenn das verwendete OS am ende mit der applikation um die rechenleistung kaempft oder alleine schon wegen den stacks speicher auslagert, ist multithreading einfach nur dumm und kontraproduktiv.
frenki schrieb:
weil Performance-Tests ergeben haben, dass es keinerlei messbaren Unterschied zwischen Fire-And-Forget-Thread und Thread-Pool gibt.
dann waren deine tests nicht representativ.
es gibt nicht um sonst threadpools, die vom design und implementieren her aufwendiger sind.
simples beispiel ist make, bau ein _grosses_ projekt mit weit mehr threads als du cores hast, es wird laenger dauern.
-
simples beispiel ist make, bau ein _grosses_ projekt mit weit mehr threads als du cores hast, es wird laenger dauern.
Das liegt aber daran dass man die Caches thrasht, und nicht daran dass das Erzeugen/Verwalten/Freigeben von Threads so lange dauert. Und vielleicht noch daran dass Disk-IO schneller geht, wenn nicht 100 Threads gleichzeitg 100 verschiedene Files lesen/schreiben, sondern alles hübsch hintereinander gelesen/geschrieben wird.
Bei Client-Server Anwendungen fällt das Erstellen eines Threads wirklich nicht sehr ins Gewicht. Viel schwerer wiegen da z.B. DB-Verbindungen. Diese zu poolen ist viel wichtiger als Threads zu poolen. Ich persönlich würde beides machen, kann man natürlich auch schön kombinieren (jeder Thread im Pool bekommt "seine" DB-Verbindung, die er auch nichtmehr hergibt solange er läuft).
-
hustbaer schrieb:
simples beispiel ist make, bau ein _grosses_ projekt mit weit mehr threads als du cores hast, es wird laenger dauern.
Das liegt aber daran dass man die Caches thrasht,
nein, an caches liegt es nicht, aber allgemein kann man sagen, es liegt daran das eine knappe resource geteilt wird.
und nicht daran dass das Erzeugen/Verwalten/Freigeben von Threads so lange dauert.
wann sprach ich davon dass es daran liegt?
Und vielleicht noch daran dass Disk-IO schneller geht, wenn nicht 100 Threads gleichzeitg 100 verschiedene Files lesen/schreiben, sondern alles hübsch hintereinander gelesen/geschrieben wird.
s.o.
Bei Client-Server Anwendungen fällt das Erstellen eines Threads wirklich nicht sehr ins Gewicht.
je nach anwendungsfall faellt es in gewicht.
Viel schwerer wiegen da z.B. DB-Verbindungen. Diese zu poolen ist viel wichtiger als Threads zu poolen. Ich persönlich würde beides machen, kann man natürlich auch schön kombinieren (jeder Thread im Pool bekommt "seine" DB-Verbindung, die er auch nichtmehr hergibt solange er läuft).
ja, knappe resourcen sollte man nicht ueberrennen, sonst verschlucken sie sich. das ist keine DB spezifische sache.
Wegen dieser fehlerhaften vorgehenweisen haben einige datenbanken selbst schon queues fuer requests. denen ist es entsprechend egal wieviele threads dann requesten, allerdings hat man dann auf einem system eventuell zwei threadpools, das reicht schon damit es wieder suboptimal laeuft.
-
rapso schrieb:
hustbaer schrieb:
simples beispiel ist make, bau ein _grosses_ projekt mit weit mehr threads als du cores hast, es wird laenger dauern.
Das liegt aber daran dass man die Caches thrasht,
nein, an caches liegt es nicht, aber allgemein kann man sagen, es liegt daran das eine knappe resource geteilt wird.
und nicht daran dass das Erzeugen/Verwalten/Freigeben von Threads so lange dauert.
wann sprach ich davon dass es daran liegt?
Ich hatte es so interpretiert. Wenn du was anderes gemeint hast, war auf jeden Fall nicht klar was.
Viel schwerer wiegen da z.B. DB-Verbindungen. Diese zu poolen ist viel wichtiger als Threads zu poolen. Ich persönlich würde beides machen, kann man natürlich auch schön kombinieren (jeder Thread im Pool bekommt "seine" DB-Verbindung, die er auch nichtmehr hergibt solange er läuft).
ja, knappe resourcen sollte man nicht ueberrennen, sonst verschlucken sie sich. das ist keine DB spezifische sache.
Wegen dieser fehlerhaften vorgehenweisen haben einige datenbanken selbst schon queues fuer requests. denen ist es entsprechend egal wieviele threads dann requesten, allerdings hat man dann auf einem system eventuell zwei threadpools, das reicht schon damit es wieder suboptimal laeuft.Ja logisch sind DB-Connections nicht das einzige. Aber halt sehr häufig anzutreffen. Und eine DB-Connection ist oft ziemlich "heavy". Da geht's nicht nur um Requests, sondern einfach um die Connection selbst.
Was Requests angeht: das erwarte ich mir schon von einer Datenbank, dass die selbst dafür sorgt Requests so abzuarbeiten dass es nix ausmacht wenn man 1000 gleichzeitig absetzt.