Ich arbeite am Design einer Anwendung, die aus drei Teilen besteht:
- Ein einzelner Thread, der bestimmte Ereignisse überwacht (Dateierstellung, externe Anforderungen usw.)
- N Worker-Threads, die auf diese Ereignisse reagieren, indem sie sie verarbeiten (jeder Worker verarbeitet und verbraucht ein einzelnes Ereignis, und die Verarbeitung kann eine variable Zeit dauern)
- einen Controller, der diese Threads verwaltet und die Fehlerbehandlung durchführt (Neustart der Threads, Protokollierung der Ergebnisse)
Obwohl dies ziemlich einfach und nicht schwer zu implementieren ist, frage ich mich, was der "richtige" Weg wäre, dies zu tun (in diesem konkreten Fall in Java, aber auch Antworten auf höhere Abstraktionen werden geschätzt). Zwei Strategien kommen in den Sinn:
Observer / Observable: Der beobachtende Thread wird vom Controller beobachtet. Im Falle eines Ereignisses wird der Controller benachrichtigt und kann die neue Aufgabe einem freien Thread aus einem wiederverwendbaren zwischengespeicherten Thread-Pool zuweisen (oder die Aufgaben in der FIFO-Warteschlange abwarten und zwischenspeichern, wenn alle Threads gerade beschäftigt sind). Die Worker-Threads implementieren Callable und geben entweder erfolgreich das Ergebnis (oder einen booleschen Wert) zurück oder geben mit einem Fehler zurück. In diesem Fall kann der Controller entscheiden, was zu tun ist (abhängig von der Art des aufgetretenen Fehlers).
Produzent / Konsument : Der überwachende Thread teilt eine BlockingQueue mit dem Controller (Ereigniswarteschlange) und der Controller teilt zwei mit allen Workern (Taskwarteschlange und Ergebniswarteschlange). Im Falle eines Ereignisses stellt der überwachende Thread ein Task-Objekt in die Ereigniswarteschlange. Der Controller entnimmt neue Aufgaben aus der Ereigniswarteschlange, überprüft sie und stellt sie in die Aufgabenwarteschlange. Jeder Mitarbeiter wartet auf neue Aufgaben und entnimmt / verbraucht diese aus der Aufgabenwarteschlange (wer zuerst kommt mahlt zuerst, wird von der Warteschlange selbst verwaltet) und stellt die Ergebnisse oder Fehler zurück in die Ergebniswarteschlange. Schließlich kann die Steuerung die Ergebnisse aus der Ergebniswarteschlange abrufen und im Fehlerfall die entsprechenden Schritte ausführen.
Die Endergebnisse beider Ansätze sind ähnlich, unterscheiden sich jedoch geringfügig:
Mit Observers erfolgt die Steuerung von Threads direkt und jede Aufgabe wird einem bestimmten neuen Worker zugeordnet. Der Aufwand für die Erstellung von Threads ist möglicherweise höher, aber dank des zwischengespeicherten Thread-Pools nicht sehr hoch. Auf der anderen Seite wird das Observer-Muster auf einen einzelnen Observer anstatt auf mehrere reduziert, wofür es nicht genau entwickelt wurde.
Die Warteschlangenstrategie scheint einfacher zu erweitern zu sein, zum Beispiel ist das Hinzufügen mehrerer Produzenten anstelle von einem einfach und erfordert keine Änderung. Der Nachteil ist, dass alle Threads unbegrenzt ausgeführt werden, selbst wenn überhaupt keine Arbeit ausgeführt wird, und die Fehler- / Ergebnisbehandlung nicht so elegant aussieht wie in der ersten Lösung.
Welcher Ansatz passt in dieser Situation am besten und warum? Ich fand es schwierig, online Antworten auf diese Frage zu finden, da die meisten Beispiele nur eindeutige Fälle behandeln, z. B. das Aktualisieren vieler Fenster mit einem neuen Wert im Observer-Fall oder das Verarbeiten mit mehreren Verbrauchern und Produzenten. Jede Eingabe wird sehr geschätzt.