Thread-Anzahl begrenzen



  • 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.



  • Belli schrieb:

    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?

    Das ist nicht offensichtlich, echt jetzt? 🙂
    Na indem zwei Threads gleichzeitig auslaufen:

    99 laufen, var = 99
    Thread 1 "endend" => 99 laufen, var = 98
    Thread 2 "endend" => 99 laufen, var = 97
    Main startet zwei neue Threads: 101 laufen, var = 99
    Und danach irgendwann beenden sich die Threads 1 und 2.

    Kannst du mit beliebig vielen Threads machen. Es sinkt dabei immer nur die Wahrscheinlichkeit, aber 100% verhindert wird es nie.



  • Mhm, kannst Du nicht die Thread-Handles verwalten? So weit ich mich erinnere, kann man mit den WaitFor... - Funktionen auf die warten, sobald Deine main-Funktion also N Threads erzeugt hat, erstellt sie erst wieder welche, wenn andere beendet sind ...



  • hustbaer schrieb:

    ...

    Okay, nu hab ichs auch verstanden.



  • @Jodocus
    Wenn du es wasserdich haben willst, dann bleibt mMn. wohl nur die sterbenden Threads zu joinen bevor du neue startest.

    Wenn du es OS-abhängig implementierst kannst du dabei u.U. weiterhin die C++ std::thread Objekte detachen. Dabei müsste der Thread sich halt beim Beenden ( DllMain(THREAD_DETACH) ) mit seinem Native-Handle ( DuplicateHandle(GetCurrentThread()) ) in einer Liste eintragen. Das Native-Handle sowie z.B. eine Node für eine Intrusive-Liste könnte man dabei bereits bei der Initialisierung des Threads öffnen/erzeugen, so dass dabei kein Code nötig ist irgendwie schief gehen könnte (z.B. Exceptions werfen). Zwischengespeichert wird das ganze dann z.B. in nem TLS-Slot mit nem Pointer auf eine per HeapAlloc angeforderte struct .

    Ist aber die Frage ob sich der ganze Aufwand und Overhead auszahlt.


Anmelden zum Antworten