Nachdem ich vergeblich nach einem Open-Source Thread-Pool für Delphi gesucht habe, welches meine Bedürfnisse abdeckt, musste ich mich dafür entschließen, einen eigenen zu schreiben. Diesen möchte ich euch hier vorstellen.
Vereinfacht kann man die Funktionsweise des ThreadPools wie folgt beschreiben:
Man verbindet sich aus einem beliebigen Thread mit dem spezifischen Manager (welcher ebenfalls ein eigenständiger Thread ist) und übergibt ihm eine Aufgabe. Der Manager übergibt die Aufgabe an einen neuen bzw. schlafenden Arbeiter oder packt sie auf die Warteliste, wenn bestimmte Grenzwerte es nicht zulassen die Aufgabe sofort abzuarbeiten. Der Arbeiter nimmt sich der Aufgabe an, arbeitet sie parallel ab und meldet sich über synchronisierte Ereignisse, wenn er fertig ist. Hat der Manager noch weitere Aufgaben anliegen, so bekommt der Arbeiter sofort den Nächsten. Sind alle Aufträge abgearbeitet, so kann der Manager eine bestimmte anzahl an Arbeitern schlafen legen (SpareWorkersCount) und die restlichen Arbeiter werden terminiert.
Hauptmerkmale
- Manager und Arbeiter sind eigenständige Threads
- Ein spezifischer Pool wird von den Basis-Klassen TPoolManager, TPoolWorker und TPoolTask abgeleitet, wobei nur wenige Methoden mit "Leben" gefüllt werden müssen.
- Für jeden abgeleiteten TPoolManager wird (nur) bei Bedarf eine eindeutige Instanz erstellt (verborgenes Singleton pattern)
- Die genaue Instanz eines spezifischen Managers ist unbekannt und muss über eine Verbindung mittels TMyPoolManager.Connect oder TMyPoolManager.Bind ermittelt werden
- Der Manager erstellt die Arbeiter gemäß der Auftragslage dynamisch unter berücksichtigung einer definierbaren Obergrenze (TMyPoolManager.ConcurrentWorkersCount)
- Die Arbeiter-Threads werden wiederverwendet. Das heißt, ist ein Arbeiter fertig und es liegen weitere Aufträge an, so bekommt er sofort den Nächsten.
- Anzahl an schlafenden Arbeitern kann über TMyPoolManager.SpareWorkersCount definiert werden
- Aufträge können einen Eigentümer haben
- Für jeden Eigentümer können Event-Handler an die Ereignisse OnTasksStatus und OnTasksComplete gebunden werden
- Aufträge können gezielt abgebrochen werden, z.B nach dem Eigentümer (TMyPoolManager.CancelTasksByOwner)
- Jeder Auftrag verfügt über die Basis-Ereignisse: OnStart, OnCancel, OnDone und kann um beliebige weitere ergänzt werden
- Aufträge können priorisiert werden
- Der Manager unterstützt den Demand-Mode: D.h. er beendet sich selbst, wenn keine Aufträge anliegen und keine Arbeiter auf Vorrat gehalten werden müssen.
- Alle instanzierten Thread-Pools (Manager und Arbeiter) können mit einer einzelnen Anweisung (z.B. bei Programmende) sauber terminiert werden: TPoolManager.TerminateAllRunningPools
- Die Freigabe eines zuvor registrierten Eigentümer kann an alle Thread-Pools mittels TPoolManager.DispatchOwnerDestroyed übermittelt werden
- Dynamsiche Anpassung von gleichzeitigen und auf Vorrat zu haltenden Arbeitern an das Ziel-System mittels TMyPoolManager.ConcurrentWorkersCountPerCPU und TMyPoolManager.SpareWorkersCountPerCPU
- Last but not least: Kein Polling.
Die Manager und Arbeiter lauschen mittels WaitFor(INFINITE) an ihren Hauptsignalen, dadurch wird kostbare Rechnerkapazität nicht sinnlos vergeudet.
Download
Damit ich das Archiv nicht bei jedem Update hier aktualisieren muss, verlinke ich am liebsten zur Projektseite bei SourceForge:
ThreadPool for Delphi (
Download |
Git-Repository)
Das Archiv enthält bereits zwei ausführliche Beispiele (auch in kompilierter Form), sodass man sowohl die Implementierung eines spezialisierten Thread-Pools nachvollziehen kann als auch dessen Verwendung.
Voraussetzung
Die
Unit ist ab Delphi 2010 lauffähig.
Lizenz
Wie ich in der Einleitung geschrieben hatte, suchte ich nach einer Open-Source Lösung, die es nicht gab. Aus diesem Grunde stelle ich meine unter die
MPL 1.1.
mfg