Wieviele Threads sollte eine Software haben?



  • Mal angenommen die Aufgaben einer Software sind gut in Teilaufgaben aufteilbar, man denke z.b. an ein Shopsystem für einen Server, bei der man sagen könnte, pro Kunde = eine Teilaufgabe, wie viele Threads sollte eine Software dann eröffnen?

    Würde man jede Teilaufgabe, also jeden Kunden in einen Thread stecken, dann wäre das progammiertechnisch bequem umsetzbar, allerdings hat eine CPU ja nicht beliebig viele Kerne.
    Ein 4 Kerner mit Hyperthreading hat bestenfalls 8 logische Cores, aus Sicht der Hardware wären meiner Vermutung nach also 8 Threads maximal keine schlechte Wahl, wobei das OS und Hintergrundprozesse selbst noch Threads aufmachen, aber gehen wir im Idealfall nur mal von der eigenen Software aus, dann wären diese 8 das maximal sinnvolle. Richtig?

    Anderseits müsste man dann ja mehrere Kunden in einem Thread verwalten und damit steigt der Verwaltungstechnische Aufwand für die Software.

    Wonach sollte man sich also orientieren?
    1. An einer möglichst einfachen Programmierung, also pro Kunde ein Thread
    oder
    2. An der Hardware und der Anzahl an Threads die diese am effizientesten Verwalten kann, also n Kunden pro Thread.

    Wichtig:
    A) Bei der Frage gehen wir mal davon aus, dass jeder Kunde einen Thread auch gut auslasten könnte, sprich, der Kunde ist selbst ein Computer und macht seinen Auftrag in Sekundenbruchteilen fertig. Z.b. ein automatisches Bestellsystem.

    😎 Falls dem aber nicht so wäre, der Kunde also ein Mensch wäre und dieser den Thread gar nicht auslasten würde, wie würde man dann vorgehen?



  • Ja, in klassischen Setups wurde sehr oft 1 Thread pro Connection benutzt, da es einfach zu implementieren ist, aber dieses Setup ist nicht wirklich skalierbar.

    Neue Programmiertechniken helfen nun aber auch weniger versierten Programmieren in die "pit of success" zu fallen. Ein (aus meiner Sicht) sehr gut gelungenes Beispiel ist das async/await-Pattern in C# welches eine sehr natürliche Programmierung von Nebenläufigkeit erlaubt. Kannst ja mal reinschnuppern.

    MfG SideWinder



  • Oh, Nachtrag: wenn ein Kunde eine Maschine ist die tatsächlich den Server so benutzt, dass nicht I/O sondern die CPU das Bottleneck ist, und du wenige solche Requests hast, dann solltest du ihm natürlich besser nicht einen Thread geben den er sich mit mehreren Nutzern teilt sondern optimalerweise so viele Threads, dass die CPU immer auf 100% laufen kann (sprich immer ein Thread pro Core vorhanden ist der 100% läuft) und so die Maschine optimal zur Verfügung stellen.

    Dieses Anwendungsgebiet ist aber ein deutlich anderes als das Otto-Normalo-HTTP-WebServer-Szenario.

    MfG SideWinder

    PS: Und generell natürlich wie immer und überall: alles stark vereinfacht und verallgemeinert.



  • @Threads
    Ich würde sagen das hängt davon ab wie viele IOs (oder allgemeiner: Vorgänge wo die CPU auf etwas warten muss was sie nicht "selbst macht") du machst und wie viel die CPU "selbst" arbeitet (Berechnungen, Daten transformieren etc.).

    Wenn du pro Teilaufgabe viel Rechenzeit brauchst und eher wenige IOs, und wenn du dir auch was Adressraum/Speicherverbrauch angeht einen Thread pro Connection leisten kannst (Stack und was ein Thread sonst noch an Speicher/Adressraum belegt), dann mach einen Thread pro Connection.
    Dass du dabei u.U. auf wesentlich mehr Threads kommst als die CPU "in Hardware" kann ist klar, aber i.A. kein Problem. Bzw. auch notwendig, damit die CPU auch 'was zu tun hat während eine Connection gerade wartet dass ein IO fertig wird.

    Wenn du pro Teilaufgabe dagegen relativ wenig Rechenzeit im Vergleich zu der IO-Anzahl brauchen wirst, UND es wirklich wichtig ist dass die Anwendung so gut wie möglich skaliert, dann kannst du dir überlegen die andere Variante zu machen.

    Was jetzt im Wesentlichen glaube ich das selbe ist wie das was SideWinder geschrieben hat, nur etwas anders formuliert. Und mit leichten Hang zu "1 Thread pro Connection" -- weil ich der Meinung bin dass es wesentlich einfacher umzusetzen ist, und die Fälle wo man damit merklich schlechter fährt eher rar sind.



  • SideWinder schrieb:

    Neue Programmiertechniken helfen nun aber auch weniger versierten Programmieren in die "pit of success" zu fallen. Ein (aus meiner Sicht) sehr gut gelungenes Beispiel ist das async/await-Pattern in C# welches eine sehr natürliche Programmierung von Nebenläufigkeit erlaubt.

    Kennst du vielleicht Benchmarks wo "async/await" mit "1 Thread pro Connection" verglichen wird? Mir ist klar dass man "1 Thread pro Connection" mit selbstgebastelten IO Demultiplexing-Techniken deutlich schlagen kann (passende Anwendung vorausgesetzt). "async/await" hat allerdings auch einiges an Overhead, und da würde es mich interessieren ob es sich, rein was Performance angeht, wirklich auszahlt.



  • Nein, keine ernsthaften Benchmarks. Ich lese den Blog von Stephen Cleary, und async/await ist - solange I/O der limitierende Faktor ist - auf jeden Fall ein großer Fortschritt zu 1-Thread-per-Client. Wie weit es von einer handgestrickten Lösung entfernt ist kann ich nicht sagen. Für unsere Verhältnisse reicht es, wir haben sehr große Anwendungen, aber nur Intranet, daher nicht so hohe Spitzen zu verkraften und zumeist bekannte Mengengerüste (sprich well-known magnitude of users / requests / etc.)

    MfG SideWinder



  • @Threads
    Das wichtigste beim Entwickeln eines servers waere diesen flexibel zu halten, damit du verschiedene verfahren ausprobieren kannst. Du kannst kaum im voraus wissen was der limitierende faktor ist. Es kann sein dass du mehrere threads pro client brauchst (wenn du z.b. bilder encoden wuerdest), kann sein, dass du nur einen thread insgesammt brauchst (weil du z.b. pro user eine datei bearbeitest und die eh mit atomic blokiert ist).
    Einige moderne server nutzen IOCP und desgleichen, manche nutzen fiber oder job/task-systeme um eine gute auslastung zu haben. manche benutzen GPU beschleuniger (z.b. zum packen pro-user signierter dokumente).

    deswegen halte das system flexibel,
    -kapsle user/clients ab
    -kapsle verbindungen
    -kapsle aufwendige schnittstellen (z.b. datenbankzugriffe)
    -unterteile requests in stufen z.b. input, parsen, verarbeiten, komprimieren, verschicken
    -bastel dir gutes logging, damit du statistiken hast ueber jeden request und jede stufe der verarbeitung sowie zum gesammtsystem. (Das OS bietet oft statistiken z.b. zu CPU auslastung die du periodisch loggen koenntest).
    -bastel dir eine testsuit die das system auslasten kann wie es reale aber auch absurde abfragepattern koennen.

    schlussendlich wirst du auch nie ein system programmieren, dass jede belastung aushaelt. Wichtiger ist quality of service, heisst, wie verhaelt sich das system wenn es ueberlastet ist? was ist wenn du 110% der anfragen bekommst die du verarbeiten kannst?



  • Kommt halt auch auf die anderen Anforderungen an.
    Wenn Sicherheit sehr wichtig ist (im Sinne von: Der Server darf nicht abstürzen) dann ist wohl der Ansatz mit fork am Besten - dann hast du sogar einen Prozess pro Client.
    Ansonsten ist der asynchrone Ansatz sicher nett. So viele Worker-Threads wie die Hardware an Kernen hergibt (inklusive Hyperthreading bei Intel), asynchron wird denen Arbeit zugeteilt (I/O ist keine Arbeit), je nach Systemarchitektur über eine prozesslokale, blockierende Queue oder über einen Queue-Server. (zb. JMS in Java)


Anmelden zum Antworten