Thread-Anzahl begrenzen



  • Jodocus schrieb:

    Ich möchte einfach von vornherein festlegen, dass höchstens N Threads gleichzeitig existieren.

    Ähm, na dann mach halt einfach nie mehr als N Threads!? Ist doch dein Code, wo liegt das Problem!?



  • dot schrieb:

    Jodocus schrieb:

    Ich möchte einfach von vornherein festlegen, dass höchstens N Threads gleichzeitig existieren.

    Ähm, na dann mach halt einfach nie mehr als N Threads!? Ist doch dein Code, wo liegt das Problem!?

    Weil ich nicht weiß, wie ich die Anzahl an aktiven Threads, die mein Programm erstellt hat, ohne Race-Condition zählen kann. Der Thread selbst kann den Counter nicht verringern, denn wenn er es täte, könnte ein anderer Thread starten, während der erste noch nicht beendet ist.



  • http://en.cppreference.com/w/cpp/atomic/atomic/fetch_add 😉

    Viel wichtiger wär aber mal die Frage, wieso deine Anwendung einfach so Threads erzeugt. Das wirkt mir irgendwie äußerst merkwürdig. Insbesonderen wenn es um einen Thread Pool gehen soll. Da erzeugt man normal einmal die nötige Anzahl Threads (z.B. mit in einer Schleife von 1 bis N) und verwendet die dann und fertig...



  • Klar rate condition - aber was denkst du wofuer es Synchronisationsfunktionem/prinzipien gibt - bist du mit thread und nebenlaeufigkeit usw. Fit oder nur am probieren?



  • Jodocus schrieb:

    dot schrieb:

    Jodocus schrieb:

    Ich möchte einfach von vornherein festlegen, dass höchstens N Threads gleichzeitig existieren.

    Ähm, na dann mach halt einfach nie mehr als N Threads!? Ist doch dein Code, wo liegt das Problem!?

    Weil ich nicht weiß, wie ich die Anzahl an aktiven Threads, die mein Programm erstellt hat, ohne Race-Condition zählen kann. Der Thread selbst kann den Counter nicht verringern, denn wenn er es täte, könnte ein anderer Thread starten, während der erste noch nicht beendet ist.

    Dann berücksichtige das bei der Festlegung von N.



  • Du wirst doch sicher eine Art Controller für die Threads haben, oder nicht?.zB die Unit, aus der heraus Du die Threads erzeugst. Dort würde ich den Zähler mitlaufen lassen. Dann noch den Thread beim Beenden dem Controller signalisieren lassen, dass er beendet ist. Unter Windows hab ich das mit Nachrichten gelöst... Der Controller kann dann den Zähler auch wieder verringern. Könntest den Zähler aber auch ganz weglassen und neue Threads nur erzeugen, wenn ein Beendigungssignal von einem der laufenden Threads kommt.

    Disclaimer: Ich bin kein Profi-Programmierer... Aber diese Lösung hat bei mir einwandfrei funktioniert (auch wenn der Hintergrund ein anderer war).



  • dot schrieb:

    Viel wichtiger wär aber mal die Frage, wieso deine Anwendung einfach so Threads erzeugt. Das wirkt mir irgendwie äußerst merkwürdig. Insbesonderen wenn es um einen Thread Pool gehen soll. Da erzeugt man normal einmal die nötige Anzahl Threads (z.B. mit in einer Schleife von 1 bis N) und verwendet die dann und fertig...

    Klar. 😉 Aber wenn ein Burst an Requests in der Aufgabenqueue landet (deren Abarbeitung z.T. zeitaufwändig ist oder auch den Thread blockieren kann, z.B. beim verbinden mit einer Datenbank, man in Datei schreibt oder allgemein eine Ressource lockt), muss ich mehr Threads erzeugen, um die sleeping Threads zu ersetzen, bis sie wieder laufen und der Burst abgearbeitet ist. Es werden also immer dann mehr Threads dazukommen, wenn nicht genug Worker-Threads bereit sind, um Aufgaben aus der Queue abzuarbeiten. So ein Burst darf aber auf keinen Fall dazu führen, dass die Anwendung auf einmal spontan 500 Threads erzeugt und das ganze System lahmlegt, daher eine Obergrenze, am liebsten softwareseitig und plattformunabhängig, aber auf jeden Fall mit Garantie.

    Belli schrieb:

    Dann berücksichtige das bei der Festlegung von N.

    Wie genau meinst du das? Etwa, dass ich sporadisch N lieber etwas kleiner festlege als das reale N?

    Gast3 schrieb:

    Klar rate condition - aber was denkst du wofuer es Synchronisationsfunktionem/prinzipien gibt - bist du mit thread und nebenlaeufigkeit usw. Fit oder nur am probieren?

    Klar bin ich nur am Probieren, aber das heißt nicht, dass ich nicht eine gewisse Ahnung von Multithreading habe. Um meine Expertise geht's hier erst mal auch gar nicht.

    Million Voices schrieb:

    Du wirst doch sicher eine Art Controller für die Threads haben, oder nicht?.zB die Unit, aus der heraus Du die Threads erzeugst. Dort würde ich den Zähler mitlaufen lassen. Dann noch den Thread beim Beenden dem Controller signalisieren lassen, dass er beendet ist.

    Schon klar, das wäre die offensichtliche Lösung, aber sie ist leider falsch:

    if(counter.fetch_add(1, memory_order_relaxed) >= max_threads)
       counter.fetch_sub(1, memory_order_relaxed); // too many threads, don't add
    else thread([&counter]() {
       /* some work ...  */
       counter.fetch_sub(1, memory_order_relaxed); // decrease counter when thread exits
    }).detach();
    

    Der Counter wird verringert, noch während der Thread aktiv ist. Wenn gerade ein Burst abgearbeitet wird und alle Threads nur so in den Startlöchern sitzen, um zusätzliche Threads zu beantragen, rennen sie genau in diese Race Condition und erzeugen mehr Threads als erlaubt.



  • Jodocus schrieb:

    Belli schrieb:

    Dann berücksichtige das bei der Festlegung von N.

    Wie genau meinst du das? Etwa, dass ich sporadisch N lieber etwas kleiner festlege als das reale N?

    Nö. Angenommen, Du willst max. 100 Threads haben. In einem läuft Deine main - Funktion, Du darfst also max. 99 weitere Threads starten. Dafür verwaltest Du einen Counter, ein weiterer Thread wird also nur gestartet, wenn der Counter noch nicht die maximale Anzahl erreicht hat. Jeder Thread, der zu Ende läuft, dekrementiert diesen Counter wieder.
    Dein Argument war ja nun, dass nach dem Dekrement in einem Thread aus main heraus bereits wieder ein Thread gestartet werden könnte, bevor der den Counter dekrementierende Thread tatsächlich auch zu Ende ist (btw. na und? Das ist eine Überschneidung von einem Sekundenbruchteil).
    Um nun also die Gesamtzahl von 100 Threads in keinem Fall zu überschreiten, startet main max. 98 weitere Threads, bzw. genauer, so lange, bis der Counter max. 98 erreicht hat.

    Damit hast Du den von Dir gezeigten Fall abgefangen. Du hast also sozusagen einen Puffer von einem einzigen Thread.

    Was soll überhaupt Dein 'reales N' sein?
    Es geht doch um eine max. Anzahl von Threads, die Deine Anwendung niemals überschreiten soll - das ist dann doch Dein 'reales N'?!



  • Warum nimmst Du keinen Threadpool mit einer festen Anzahl Threads und einer vorgeschalteten Queue, in die die Requests hinein geschrieben werden? Dann blockiert ein lange laufender Request zwar einen Thread im Pool, aber es Du kannst ja (hoffentlich) grob abschätzen, wieviele solcher lang laufender Requests Du erwartest.

    Ansonsten kann ein Thread im Pool, z.B. anhang des Request-Typen, entscheiden, ob er die Anzahl Threads im Pool vergrößert und nach Ende des Requests - wann immer das sein mag - wieder verkleinert. Wäre wahrscheinlich nicht mein Ansatz. Ich nutze meistens eine feste Anzahl Threads im Pool, der meistens doppelt soviele Threads hält, wie der Rechner Kerne hat. (Faustregel)



  • Belli schrieb:

    Damit hast Du den von Dir gezeigten Fall abgefangen.

    Nö. Bloss unwahrscheinlicher gemacht.



  • Sorry, ich versteh nicht was dein Problem mit dem Countrt ist. Du hast ein Limit von Max N. Threads und du erstellst nicht mehr Threads als N.
    Du hast ein Atomic int und jeder Thread der gestartet wird erhöht und jeder Thread der seine Arbeit beendet reduziert den Counter.

    Wie kann da deiner Meinung nach jetzt etwas schief gehen?

    Vielleicht sollte man auch mal das Design in Frage stellen.
    Machen deine vielen Threads überhaupt Sinn?
    Wie ein Vorposter schon richtig gemeint hat machen mehr Threads als Prozessorkerne * 2 nur selten Sinn. Da du dann das Problem mit Thread oversubscription kriegst. Mit anderen Worten dein System ist dann mehr damit beschäftigt von Thread zu Thread zu wechseln statt die eigentliche Arbeit zu erledigen.



  • hustbaer schrieb:

    Belli schrieb:

    Damit hast Du den von Dir gezeigten Fall abgefangen.

    Nö. Bloss unwahrscheinlicher gemacht.

    Hab ich da n Denkfehler drin? Auf welche Weise sollen jetzt mehr als 100 Threads zu einer Zeit entstehen?



  • Jodocus schrieb:

    dot schrieb:

    Jodocus schrieb:

    Ich möchte einfach von vornherein festlegen, dass höchstens N Threads gleichzeitig existieren.

    Ähm, na dann mach halt einfach nie mehr als N Threads!? Ist doch dein Code, wo liegt das Problem!?

    Weil ich nicht weiß, wie ich die Anzahl an aktiven Threads, die mein Programm erstellt hat, ohne Race-Condition zählen kann. Der Thread selbst kann den Counter nicht verringern, denn wenn er es täte, könnte ein anderer Thread starten, während der erste noch nicht beendet ist.

    Wenn das wahr wäre dann könnte es in c++ kein multithreading geben und sowas wie sharedpointer würde auch nicht gehen.
    Dein Counter muss Atomic sein oder mit mutexen gesichert, da sind race Conditions ausgeschlossen.
    Du könntest ein Wrapper um std::Thread bauen und,im constructor increment und,im destruktor decrement machen oder du lässt deinen Thread eine Message an den Main Thread machen die dann die add Thread Funktion vorher abarbeiten muss (mutex).



  • Ruvi schrieb:

    Sorry, ich versteh nicht was dein Problem mit dem Countrt ist. Du hast ein Limit von Max N. Threads und du erstellst nicht mehr Threads als N.
    Du hast ein Atomic int und jeder Thread der gestartet wird erhöht und jeder Thread der seine Arbeit beendet reduziert den Counter.

    Wie kann da deiner Meinung nach jetzt etwas schief gehen?

    Sieh' dir noch mal mein Codebeispiel an und überlege, was passiert, wenn der Thread unterbrochen wird, nachdem er den Counter verringert hat.

    Ruvi schrieb:

    Vielleicht sollte man auch mal das Design in Frage stellen.
    Machen deine vielen Threads überhaupt Sinn?
    Wie ein Vorposter schon richtig gemeint hat machen mehr Threads als Prozessorkerne * 2 nur selten Sinn. Da du dann das Problem mit Thread oversubscription kriegst. Mit anderen Worten dein System ist dann mehr damit beschäftigt von Thread zu Thread zu wechseln statt die eigentliche Arbeit zu erledigen.

    Keine Sorge, über Oversubscription wird sich an anderer Stelle Gedanken gemacht. Hier geht es etwa darum, blockierte Threads (kurzzeitig) zu ersetzen.

    Ruvi schrieb:

    Wenn das wahr wäre dann könnte es in c++ kein multithreading geben und sowas wie sharedpointer würde auch nicht gehen.

    Nein, das hat damit nichts zu tun.

    Ruvi schrieb:

    Dein Counter muss Atomic sein oder mit mutexen gesichert, da sind race Conditions ausgeschlossen.

    Atomic schützt nicht vor Race Conitions, nur vor trivialen Data Races. Mutexes lindern das Problem allerdings, aber weil sie langsam sind und zeitliche Überschneidungen unwahrscheinlicher machen. Aber ein this_thread::sleep_for(1ms) hat den selben Effekt.



  • Einen Wrapper um Thread kann ich nicht bauen, weil ich die Threads detache. Sie laufen völlig autonom und möglichst ohne ständige Synchronisation mit anderen Threads oder geteilten Ressourcen. Das mit der Message hilft afaics nicht gegen die Race Conition.



  • Jodocus schrieb:

    Sieh' dir noch mal mein Codebeispiel an und überlege, was passiert, wenn der Thread unterbrochen wird, nachdem er den Counter verringert hat. Das mit der Message hilft afaics nicht gegen die Race Conition.

    Einer von uns beiden steht gerade gehoerig auf dem Schlauch und ich bin mir nicht sicher wer es ist.

    Wie kann denn bitte der Thread unterbrochen werden nachdem Du den Counter innerhalb des Threads verringert hast? Das sollte doch die letzte Operation/Instruktion sein die der Thread in seiner Lebensspanne vornimmt bevor er beendet wird.

    Ist dein Problem jetzt wirklich ,dass dein Thread nach der letzten Instruktion (counter decrement) eine hypothetische Zeitspanne von x ms noch weiterlebt?
    Das ist eher ein spirituelles, wenn ein richtiges Problem.

    Edit:

    Jodocus schrieb:

    Der Counter wird verringert, noch während der Thread aktiv ist. Wenn gerade ein Burst abgearbeitet wird und alle Threads nur so in den Startlöchern sitzen, um zusätzliche Threads zu beantragen, rennen sie genau in diese Race Condition und erzeugen mehr Threads als erlaubt.

    Das ist meines wissens grundlegend falsch.
    Eine Atomic variable kann sich nur einmal im Speicher eines Prozessors befinden.
    *Genauer: Das Betriebssystem muss sicherstellen, dass sobald eine Instruktion die eine Atomic Variable betrifft ausgefuehrt wird, diese atomic Variable in keinem anderen Prozessor gerade (im selben Moment) benutzt/ausgefuehrt wird.

    Beispiel:
    Wenn das System das nicht sicherstellen wuerde, koennten naemlich folgende Probleme auftreten.

    Du hast Thread A und Thread B und ein atomic<int> mit Wert 0.
    Wenn beide Threads zur exakt gleichen Zeit ein +1 ausfuehren wuerde, haettest Du am Ende einen Wert von 1 statt 2.
    Denn der Prozessor laedt die Variable erhoeht den Wert den er gelesen hat um 1 und schreibt dann die Zahl wieder zurueck, wenn beide Threads das gleichzeitig machen, wuerden beide Threads den Wert 0 lesen ihn +1 rechnen und dann zurueckschreiben.

    Es ist sogar absolut unmoeglich, wenn ich mir deinen Code angucke denn Du checkst deinen Counter mit einer "Add" Konstruktion.
    Mit anderen Worten es ist unmoeglich, dass alle "Burst" Threads die selbe Zahl zur selben Zeit sehen, denn das ist ja der Sinn von atomic Variablen, deswegen versteh ich auch nicht wieso Du meinst das sei eine Race Condition.

    Wenn der counter decrement die letzte Instruktion innerhalb des Threads ist, kann es hoechstens passieren, dass fuer einne "mystischen Akademischen" Moment genau ein einziger Thread mehr existiert als beabsichtigt.



  • Ruvi schrieb:

    Wie kann denn bitte der Thread unterbrochen werden nachdem Du den Counter innerhalb des Threads verringert hast? Das sollte doch die letzte Operation/Instruktion sein die der Thread in seiner Lebensspanne vornimmt bevor er beendet wird.

    Woher willst du das wissen? Es können noch X Destruktoren kommen, was aber sowieso vollkommen egal ist, da es alleine in der Hand der Implementierung von std::thread liegt, wie lange der Software-Thread nach Verlassen der User-Funktion weiterlebt.

    Ruvi schrieb:

    Ist dein Problem jetzt wirklich ,dass dein Thread nach der letzten Instruktion (counter decrement) eine hypothetische Zeitspanne von x ms noch weiterlebt?
    Das ist eher ein spirituelles, wenn ein richtiges Problem.

    Nein, ist es nicht. Unter gewissen heavy-Load-Bedingungen, die ich in meinen Tests einsellen kann, komme ich auf genau dieses Szenario. Ich brauche an der Stelle keine Spekulationen über W'keiten, mit der solche Situationen auftreten, sowas darf einfach nicht passieren.

    Ruvi schrieb:

    Edit:

    Jodocus schrieb:

    Der Counter wird verringert, noch während der Thread aktiv ist. Wenn gerade ein Burst abgearbeitet wird und alle Threads nur so in den Startlöchern sitzen, um zusätzliche Threads zu beantragen, rennen sie genau in diese Race Condition und erzeugen mehr Threads als erlaubt.

    Das ist meines wissens grundlegend falsch.
    Eine Atomic variable kann sich nur einmal im Speicher eines Prozessors befinden.
    Es ist sogar absolut unmoeglich, wenn ich mir deinen Code angucke denn Du checkst deinen Counter mit einer "Add" Konstruktion.
    Mit anderen Worten es ist unmoeglich, dass alle "Burst" Threads die selbe Zahl zur selben Zeit sehen, denn das ist ja der Sinn von atomic Variablen, deswegen versteh ich auch nicht wieso Du meinst das sei eine Race Condition.

    Du redest hier doch am Thema vorbei. Du hast die Situation doch schon ein paar Zeilen weiter oben selbst erörtert, aber gleich von der Hand gewiesen: Der Thread kann unterbrochen werden, nachdem er den Counter verringert hat. Ein anderer Thread kann nun einen neuen starten, real laufen dann mehr Threads als erlaubt. Was ist daran so schwer zu verstehen? 😕

    Jodocus schrieb:

    Wenn der counter decrement die letzte Instruktion innerhalb des Threads ist, kann es hoechstens passieren, dass fuer einne "mystischen Akademischen" Moment genau ein einziger Thread mehr existiert als beabsichtigt.

    Es ist schon interessant, was für dich mystisch akademisch ist. Eigentlich interessiert's mich nicht, aber hälst du ein klassisches ABA-Problem auch für mystisch akademisch? Ich habe es wie gesagt auf meiner Maschine getestet (kannst du gerne auch mal probieren, wenn du es nicht glaubst). Und selbst, wenn es bei dir nicht passiert, musst du es trotzdem hinnehmen, dass es bei mir passiert. Es ist theoretisch absolut möglich und real bei mir der Fall.

    Edit: Und genau einer muss es erst nicht sein. Angenommen, du hast N Threads, die unterbrochen werden, nachdem sie den Counter verringert haben. Dann können bis zu N Threads über Limit entstehen. Sorry, aber wenn du mir wirklich helfen möchtest, musst du noch mal etwas gründlicher über die Situation nachdenken.



  • Ruvi schrieb:

    Du hast Thread A und Thread B und ein atomic<int> mit Wert 0.
    Wenn beide Threads zur exakt gleichen Zeit ein +1 ausfuehren wuerde, haettest Du am Ende einen Wert von 1 statt 2.
    Denn der Prozessor laedt die Variable erhoeht den Wert den er gelesen hat um 1 und schreibt dann die Zahl wieder zurueck, wenn beide Threads das gleichzeitig machen, wuerden beide Threads den Wert 0 lesen ihn +1 rechnen und dann zurueckschreiben.

    Deine Erklärungen sind ja nett gemeint, aber du kannst mir schon ruhig glauben, dass ich weiß, wie das Memory-Model von C++ funktioniert. 😉



  • Ich habe keine Ahnung was Du mit zig Destruktoren meinst, ausser den Thread Destruktor hast Du doch voellig in der Hand wo Du das counter dekrement machst.

    Wie gesagt Dein --Counter sollte die letzte Instruktion innerhalb des Threads sein (vom Thread Destruktor) mal abgesehen.

    Falls Du das schon so hast, behaupte ich einfach mal das dein "Problem" nicht geloest werden kann, jedenfalls nicht in der Granularität die Du gerne haettest.
    Mir faellt jedenfalls keine Loesung ein die Dir hier nicht schon angeboten wurde.



  • Ruvi schrieb:

    Ich habe keine Ahnung was Du mit zig Destruktoren meinst, ausser den Thread Destruktor hast Du doch voellig in der Hand wo Du das counter dekrement machst.

    Im Thread selbst werden Objekte erzeugt, die ihrerseits Destruktoren haben.


Anmelden zum Antworten