Fork-Join Framework

Parallele Programmierung (3IB)

Prof. Dr.-Ing. Sandro Leuchter
Hochschule Mannheim, Fakultät für Informatik
Wintersemester 2024/2025

 

Dieses Werk ist lizenziert unter einer Creative Commons „Namensnennung – Nicht-kommerziell – Weitergabe unter gleichen Bedingungen 4.0 International“ Lizenz.

Überblick

=> Grundlage: Hettel und Tran (2016, Kap. 13)

  • Fork-Join Framework
    • Kontrollfluss
    • Rekursion
  • Programmiermodell
    • ForkJoinPool und “Work-Stealing”
    • ForkJoinTask
    • unterschiedliche Ausführungsmodi
  • Kleine Beispiele (Array-Verarbeitung)
    • RecursiveAction (Filter)
    • RecursiveTask (Summieren)

“Fork-Join” Entwurfsmuster

Kontrollfluss beim Entwurfsmuster “Fork-Join”

  • Kontrollfluss wird an einer Stelle in mehrere nebenläufige Flüsse aufgeteilt (“fork”), die an einer späteren Stelle alle wieder vereint (“join”) werden.
  • Dies ist immer dann anwendbar, wenn ein komplexes Problem in kleinere gleichartige Teilprobleme zerlegt werden kann, die unabhängig voneinander und damit potenziell parallel gelöst werden können.
  • Die Lösungen der Teilprobleme können dann zur Gesamtlösung kombiniert werden.

Hettel und Tran (2016, 183)

Rekursive Verwendung

  • Besonders geeignet für rekursive Algorithmen
    (“Divide and Conquer”):
    • In der ersten Phase wird das Problem immer wieder zerkleinert (Divide-Phase).
    • Ist eine entsprechende Problemgröße erreicht, werden die Teilaufgaben gelöst (Work-Phase) und
    • anschließend das Ergebnis zusammengesetzt (Combine-Phase).

Hettel und Tran (2016, 185)

Programmiermodell des Fork-Join Frameworks

ForkJoinPool und “Work-Stealing” im Threadpool

ForkJoinPool (seit Java 7) implementiert ExecutorService:

  • Standard-Pool bereits instanziiert: ForkJoinPool.commonPool() (seit Java 8)
  • “Using the common pool normally reduces resource usage (its threads are slowly reclaimed during periods of non-use, and reinstated upon subsequent use)”1
  • Parallelität wird im Konstruktor vorgegeben
    • Default-Wert: Runtime.getRuntime().availableProcessors() (max. 32767)
  • Eigener Scheduler: Alle Threads im Pool versuchen anstehende Subtasks zu finden und zu übernehmen, die von anderen aktiven Tasks erzeugt wurden (“Work-Stealing”).
    • Threads, die keine Aufgaben mehr zu erledigen haben, können Aufgaben von anderen Threads übernehmen (“stehlen”), die gerade beschäftigt sind.
    • Aufgaben werden in Queues gespeichert.
  • Effiziente Verarbeitung, wenn die meisten Tasks Subtasks erzeugen.

Programmiermodell

  • ForkJoinTask<E>: leichtgewichtiges Future<E> (soll nicht blockieren)

  • RecursiveAction: Kein Rückgabewert (nur Seiteneffekt, z.B. bei in-place/in situ Sortierung)

  • RecursiveTask<E>: Funktion mit Rückgabewert (wird z. B. vom “Über-Task” mit join() geholt und in Ergebnis eingebaut)


Ausführungsmodell

Java SE 1.7 API-Dok: java.util.concurrent.ForkJoinPool

Kleine Beispiele

Laboraufgabe “in-situ Filterung als divide and conquer

  • Projekt: pp.12.01-ForkJoinArrayFilter
  • Bearbeitungszeit: 25 Minuten
  • Musterlösung: 15 Minuten
  • Kompatibilität: mindestens Java SE 10

  • Das Fork-Join Framework eignet sich besonders gut zur Implementierung von Array-Algorithmen.
  • Alle Werte eines Arrays mit MAX überschreiben, die > MAX sind.
  • Das Array soll halbiert werden, bis \(\le\) 4 Elemente übrig sind.

Hettel und Tran (2016, 191 (modifiziert))

Laboraufgabe “Reduce als divide and conquer

  • Projekt: pp.12.02-ForkJoinArrayReduce
  • Bearbeitungszeit: 30 Minuten
  • Musterlösung: 20 Minuten
  • Kompatibilität: mindestens Java SE 10

  1. initialisieren (ARRAY_LEN Elemente)
  2. Array halbieren, bis \(\le\) SLICE_LEN Elem.
  3. summieren (Hälften oder \(\le\) SLICE_LEN Elem.)
  • Laufzeituntersuchung:
    • UV: SLICE_LEN, ARRAY_LEN
    • AV: Dauer in ms

Hettel und Tran (2016, 191)

Referenzen

Hettel, Jörg und Manh Tien Tran. 2016. Nebenläufige Programmierung mit Java. Konzepte und Programmiermodelle für Multicore-Systeme. Heildelberg: dpunkt.verlag.