Frage

Ich versuche, einen Entwurf für einen Thread-Pool mit vielen Designanforderungen für meinen Job zu entwickeln.Dies ist ein echtes Problem für funktionierende Software und eine schwierige Aufgabe.Ich habe eine funktionierende Implementierung, aber ich möchte sie SO vorlegen und sehen, welche interessanten Ideen die Leute haben, damit ich sie mit meiner Implementierung vergleichen und sehen kann, wie sie abschneidet.Ich habe versucht, so genau wie möglich auf die Anforderungen einzugehen.

Der Thread-Pool muss eine Reihe von Aufgaben ausführen.Die Aufgaben können von kurzer Dauer (<1 Sekunde) oder von langer Dauer (Stunden oder Tage) sein.Jeder Aufgabe ist eine Priorität zugeordnet (von 1 = sehr niedrig bis 5 = sehr hoch).Aufgaben können jederzeit eintreffen, während die anderen Aufgaben ausgeführt werden. Wenn sie also eintreffen, muss der Thread-Pool diese abholen und planen, sobald Threads verfügbar werden.

Die Aufgabenpriorität ist völlig unabhängig von der Aufgabenlänge.Tatsächlich ist es unmöglich zu sagen, wie lange die Ausführung einer Aufgabe dauern könnte, ohne sie einfach auszuführen.

Einige Aufgaben sind CPU-gebunden, während andere stark E/A-gebunden sind.Es ist unmöglich, im Voraus zu sagen, um welche Aufgabe es sich handelt (obwohl ich vermute, dass es möglich sein könnte, dies zu erkennen, während die Aufgaben ausgeführt werden).

Das Hauptziel des Thread-Pools besteht darin, den Durchsatz zu maximieren.Der Thread-Pool sollte die Ressourcen des Computers effektiv nutzen.Im Idealfall wäre bei CPU-gebundenen Aufgaben die Anzahl der aktiven Threads gleich der Anzahl der CPUs.Für IO-gebundene Aufgaben sollten mehr Threads zugewiesen werden, als CPUs vorhanden sind, damit das Blockieren den Durchsatz nicht übermäßig beeinträchtigt.Es ist wichtig, den Einsatz von Schlössern zu minimieren und Thread-sichere/schnelle Container zu verwenden.

Im Allgemeinen sollten Sie Aufgaben mit höherer Priorität mit einer höheren CPU-Priorität ausführen (siehe:SetThreadPriority).Aufgaben mit niedrigerer Priorität sollten die Ausführung von Aufgaben mit höherer Priorität nicht „blockieren“. Wenn also eine Aufgabe mit höherer Priorität auftritt, während alle Aufgaben mit niedriger Priorität ausgeführt werden, wird die Aufgabe mit der höheren Priorität ausgeführt.

Den Aufgaben ist ein Parameter „Maximale Anzahl ausgeführter Aufgaben“ zugeordnet.Jeder Aufgabentyp darf höchstens so viele gleichzeitige Instanzen der Aufgabe gleichzeitig ausführen.Beispielsweise könnten wir die folgenden Aufgaben in der Warteschlange haben:

  • A – 1000 Instanzen – niedrige Priorität – maximale Aufgaben 1
  • B – 1000 Instanzen – niedrige Priorität – maximale Aufgaben 1
  • C – 1000 Instanzen – niedrige Priorität – maximale Aufgaben 1

Eine funktionierende Implementierung könnte (höchstens) 1 A, 1 B und 1 C gleichzeitig ausführen.

Es muss unter Windows XP, Server 2003, Vista und Server 2008 (neueste Service Packs) laufen.


Als Referenz könnten wir die folgende Schnittstelle verwenden:

namespace ThreadPool
{
    class Task
    {
    public:
        Task();     
        void run();
    };

    class ThreadPool
    {    
    public:
        ThreadPool();
        ~ThreadPool();

        void run(Task *inst);
        void stop();
    };
}
War es hilfreich?

Lösung

Was werden wir also als Grundbaustein dafür auswählen?Windows verfügt über zwei vielversprechende Bausteine: I/O Completion Ports (IOCPs) und Asynchronous Procedure Calls (APCs).Beide ermöglichen uns FIFO-Warteschlangen ohne explizite Sperrung und mit einem gewissen Maß an integrierter Betriebssystemunterstützung an Stellen wie dem Scheduler (z. B. können IOCPs einige Kontextwechsel vermeiden).

APCs passen vielleicht etwas besser, aber wir müssen mit ihnen etwas vorsichtig sein, da sie nicht ganz „transparent“ sind.Wenn das Arbeitselement eine warnbare Wartezeit ausführt (::SleepEx, ::WaitForXxxObjectEx usw.) und wir versehentlich einen APC an den Thread senden, übernimmt der neu gesendete APC den Thread und hält den zuvor ausgeführten APC an, bis der neue APC verfügbar ist fertig.Dies ist schlecht für unsere Parallelitätsanforderungen und kann die Wahrscheinlichkeit eines Stapelüberlaufs erhöhen.

Andere Tipps

Es muss unter Windows XP, Server 2003, Vista und Server 2008 (neueste Service Packs) laufen.

Welche Eigenschaften der integrierten Thread-Pools des Systems machen sie für Ihre Aufgabe ungeeignet?Wenn Sie auf XP und 2003 abzielen möchten, können Sie nicht die neuen glänzenden Vista/2008-Pools verwenden, aber Sie können immer noch QueueUserWorkItem und Freunde verwenden.

@DrPizza – das ist eine sehr gute Frage, die das Problem direkt auf den Punkt bringt.Es gibt einige Gründe, warum QueueUserWorkItem und der Windows NT-Thread-Pool ausgeschlossen wurden (obwohl der Vista-Thread-Pool interessant aussieht, vielleicht in ein paar Jahren).

Erstens wollten wir eine bessere Kontrolle darüber haben, wann Threads gestartet und gestoppt werden.Wir haben gehört, dass der NT-Thread-Pool zögert, einen neuen Thread zu starten, wenn er denkt, dass die Aufgaben zu kurz kommen.Wir könnten WT_EXECUTELONGFUNCTION verwenden, aber wir haben wirklich keine Ahnung, ob die Aufgabe lang oder kurz ist

Zweitens: Wenn der Thread-Pool bereits mit Aufgaben mit langer Laufzeit und niedriger Priorität gefüllt wäre, bestünde keine Chance, dass eine Aufgabe mit hoher Priorität rechtzeitig ausgeführt wird.Der NT-Thread-Pool hat kein wirkliches Konzept für Aufgabenprioritäten, daher können wir kein QueueUserWorkItem erstellen und sagen: „Übrigens, führen Sie das sofort aus.“

Drittens ist (laut MSDN) der NT-Thread-Pool nicht mit dem STA-Apartmentmodell kompatibel.Ich bin mir nicht ganz sicher, was das bedeuten würde, aber alle unsere Arbeitsthreads laufen in einer STA.

@DrPizza – das ist eine sehr gute Frage, die das Problem direkt auf den Punkt bringt.Es gibt einige Gründe, warum QueueUserWorkItem und der Windows NT-Thread-Pool ausgeschlossen wurden (obwohl der Vista-Thread-Pool interessant aussieht, vielleicht in ein paar Jahren).

Ja, es sieht so aus, als ob es in Vista ziemlich verbessert wurde und jetzt ziemlich vielseitig ist.

Okay, mir ist immer noch etwas unklar, wie die Prioritäten Ihrer Meinung nach funktionieren sollen.Wenn der Pool derzeit eine Aufgabe vom Typ A mit maximaler Parallelität 1 und niedriger Priorität ausführt und ihm eine neue Aufgabe ebenfalls vom Typ A (und maximaler Parallelität 1) zugewiesen wird, dieses Mal jedoch mit hoher Priorität, was soll er dann tun? ?

Das Anhalten des aktuell ausgeführten A ist schwierig (es könnte eine Sperre enthalten, die die neue Aufgabe übernehmen muss, wodurch das System blockiert wird).Es kann kein zweiter Thread erzeugt und einfach nebenher ausgeführt werden (die zulässige Parallelität beträgt nur 1).Es kann jedoch nicht warten, bis die Aufgabe mit niedriger Priorität abgeschlossen ist, da die Laufzeit unbegrenzt ist und dies dazu führen würde, dass eine Aufgabe mit niedriger Priorität eine Aufgabe mit hoher Priorität blockiert.

Meine Vermutung ist, dass Sie das letztere Verhalten anstreben?

@DrPizza:

OK, ich bin mir immer noch ein bisschen unklar, wie Sie sich wünschen, dass die Prioritäten funktionieren.Wenn der Pool derzeit eine Aufgabe vom Typ A mit maximaler Parallelität von 1 und niedriger Priorität ausführt und eine neue Aufgabe auch vom Typ A (und maximaler Parallelität 1) erteilt wird, diesmal jedoch mit einer hohen Priorität, was sollte er tun sollte ?

Dies ist eine etwas knifflige Angelegenheit, obwohl ich in diesem Fall meiner Meinung nach zufrieden damit wäre, die Aufgabe mit niedriger Priorität einfach bis zum Abschluss ausführen zu lassen.Normalerweise sehen wir nicht viele Aufgaben der gleichen Art mit unterschiedlichen Thread-Prioritäten.In unserem Modell ist es tatsächlich möglich, Aufgaben an bestimmten, genau definierten Punkten sicher anzuhalten und später neu zu starten (aus anderen Gründen), obwohl die damit verbundenen Komplikationen das Risiko wahrscheinlich nicht wert sind.

Normalerweise hätten nur unterschiedliche Aufgabentypen unterschiedliche Prioritäten.Zum Beispiel:

  • Eine Aufgabe – 1000 Instanzen – niedrige Priorität
  • B-Aufgabe – 1000 Instanzen – hohe Priorität

Angenommen, die A-Aufgaben wären angekommen und würden ausgeführt werden, und dann wären die B-Aufgaben eingetroffen, würden wir wollen, dass die B-Aufgaben mehr oder weniger sofort ausgeführt werden können.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top