===================== Master/Worker-Pattern ===================== Wir haben bislang bei jeder parallelisierten Funktion nach dem Fork-and-Join-Pattern die gewünschte Zahl von Threads erzeugt, diese die jeweiligen Teilaufgaben rechnen lassen und schließlich auf das Ende der Threads mit der `join`-Methode gewartet. Das Erzeugen und Abbauen von Threads ist jedoch nicht sehr billig. Dies demonstriert folgendes kleines Testprogramm, das die hierfür benötigte Zeit misst: :import:session16/threads.cpp ---- SHELL (path=session16) --------------------------------------------------- g++ -O3 -g -I/home/numerik/pub/hpc/session16 -std=c++11 -o threads threads.cpp ./threads ------------------------------------------------------------------------------- Da die Zahl rechenintensiver Threads ohnehin zu Beginn festgelegt wird (wie beispielsweise durch das Abfragen von `std::thread::hardware_concurrency()`), erscheint es naheliegend, diese Threads zu Beginn zu erzeugen und dann die parallel auszuführenden Teilaufgaben an diese Threads zu delegieren. Prinzipiell geht dies mit dem Master/Worker-Pattern, bei dem * der Master die parallel auszuführenden Teilaufgaben an die Worker delegiert und * die Worker auf Aufträge warten und diese dann ausführen. Wie kann so ein Auftrag repräsentiert werden? Wenn die Aufträge alle von einheitlicher Natur sind, könnte diese durch eine entsprechende uniforme `struct` beschrieben werden. In der Praxis sehen die Aufträge aber sehr unterschiedlich aus. Selbst bei unseren bisherigen Beispielen würden wir gerne parallel Matrizen initialisieren, kopieren und miteinander multiplizieren. Für die Unterstützung polymorpher Aufträge bieten sich in C++11 parameterlose Funktionsobjekte an (worunter alle Lambda-Ausdrücke fallen) und für einen Container eines polymorphen Funktionsobjekts empfiehlt sich `std::function` aus ``. Nur der Return-Typ müsste hier festgelegt werden. Im einfachsten Falle könnten wir hier mit `std::function` arbeiten. Im folgenden Ansatz repräsentiert ein Objekt der Klasse `Worker` genau einen Worker und den zugehörigen Thread. Die Liste `jobs` dient der Verwaltung der Aufträge, die dieser Worker über die `submit`-Methode erhält: :import:session16/worker.cpp Wie sieht dann sie Ausführung aus? ---- SHELL (path=session16) --------------------------------------------------- g++ -O3 -g -I/home/numerik/pub/hpc/session16 -std=c++11 -o worker worker.cpp ./worker ------------------------------------------------------------------------------- Aufgaben ======== * Was genau wird von der _capture_ `[=]` im Konstruktor erfasst? * Warum erfolgt die Initialisierung des Threads im Konstruktor nicht in einem _initializer_ wie sonst auch üblich bei Konstruktoren? * Warum genau terminiert das Programm mit `Abort (core dumped)`? * Wie ließe sich das verhindern? Korrigieren Sie `worker.cpp` so, dass das Hauptprogramm unverändert bleibt und somit nur die `Worker`-Klasse korrigiert wird. Denken Sie dabei daran, dass diese Klasse den RAII-Prinzipien genügen sollte. :navigate: up -> doc:index next -> doc:session16/page02