14.08.2009, 22:56 Uhr

Mit .NET 4 in eine parallele Zukunft?

Während die Taktfrequenzen stagnieren, besitzen moderne Prozessoren immer mehr Kerne. Mit .NET 4 und den Parallel Extensions erhalten Entwickler endlich die passenden Werkzeuge dafür.
von Jens KonerowDie Luft ist ein wenig raus. Seit Jahren erzielt die Hardware-Industrie bei der Taktfrequenz eines Prozessors keine nennenswerten Steigerungen mehr. Die technische Entwicklung geht trotzdem rasant voran, denn statt höherer Taktraten erhalten die Prozessoren immer mehr Kerne. Zwei und vier Kerne sind heutzutage Standard, 8 Kerne werden es in naher Zukunft sein und CPUs mit 64 Kernen befinden sich bereits in der Entwicklung. Wie sooft hinkt die Software der Hardware in der Entwicklung ein wenig hinterher. Aktuell ist die parallele Programmierung mit Hilfe von Threads, insbesondere deren Synchronisierung, mit viel Mehraufwand verbunden. Das kommende .NET Framework 4.0 integriert für alle auf Managed Code-Programmiersprachen die Parallel Extensions, die die parallele Entwicklung deutlich vereinfachen. Für das aktuelle .NET 3.5 SP1 stellt die Parallel FX Library dieselbe Funktionalität zur Verfügung.Die Parallel Extensions bestehen aus der Task Parallel Library (TPL), PLINQ (Paralleles LINQ) und den koordinierten Datenstrukturen (Coordination Data Structures, CDS).Die Task Parallel Library (TPL) Aktuell werden mit einer durch Multithreading realisierten Parallelisierung zwei Ziele verfolgt. Aufgaben werden in Threads ausgelagert, damit die Benutzeroberfläche reaktionsfreudig bleibt. Mehrere Threads führen Berechnungen parallel durch, um Performancevorteile aus dem Mehrkehrsystemen zu ziehen. Letztere Zielsetzung wird dadurch erschwert, dass sich eine Anwendung an die Hardwaregegebenheiten anpassen muss. Auf einem System mit einem Kern bringt es keine Vorteile, eine Berechnung auf mehrere Threads zu verteilen, denn der Scheduler wäre die meiste Zeit damit beschäftigt, den einzelnen Threads ,,Zeitscheiben" zuzuweisen. Der durch das Sichern und Wiederherstellen der Threadstati resultierende Overhead würde den Performance-Gewinn wieder auffressen. Ein Thread Pool ist nur auf den ersten Blick eine Lösung. Mit Hilfe der ThreadPool-Klasse kann ein Programm Aufgaben zwar relativ komfortabel ,,parallelisieren", die Kehrseite der Medaille ist der beschränkte Umfang des API. Zudem ist kein direkter Zugriff auf den Thread möglich, der die Aufgabe bearbeitet. Die Task Parallel Library (TPL) verbirgt die Multithreading-Mechanismen und kombiniert die Vorzüge einzeln erzeugter Threads mit denen des Thread Pools. Bei der TPL stehen bei Klassen (im neuen Namespace System.Threading.Tasks) im Mittelpunkt: Task und Parallel. Der Begriff Task (Aufgabe) wurde bewusst gewählt, wobei angemerkt sei, dass eine Task-Instanz nicht mit einem Thread korrespondiert. Aufgaben sind logische Codeblöcke, die die Runtime im Hintergrund entsprechend der vorhandenen Hardware skaliert. Der Task Scheduler hält Warteschlangen für die Kerne bereit und gewährleistet eine annähernd gleiche Auslastung. Das folgende kleine Beispiel erzeugt, startet und wartet auf eine Aufgabe. Der Lambda-Ausdruck teilt dem Konstruktor mit, welcher Code ausgeführt werden soll:. Task oTask = new Task(() => DoSomeWork()); oTask.Start();oTask.Wait();Alternativ lässt sich das Instanzieren und Starten der Aufgabe in der TaskFactory.StartNew-Methode zusammenfassen:Task oTask = Task.Factory.StartNew(() => DoSomeWork());Nicht selten müssen Aufgaben auf die Fertigstellung anderer Prozesse warten oder kommen unter bestimmten Bedingungen zum Tragen. Die ContinueWith-Methode eines Task-Objekts setzt zwei Aufgaben in Relation zueinander. Standardmäßig führt das .NET Framework die zweite Aufgabe nach der Beendigung der ersten Aufgabe aus. Mit dem zweiten Parameter wird ein spezielles Verhalten definiert:Task oTask = Task.Factory.StartNew(() => DoSomeWork());Task oNextTask = oTask.ContinueWith((t) = > { Console.WriteLine("Task {0} ist fertig", t.Id); }, TaskContinuationOptions.NotOnCanceled); oNextTask.Wait(); Das Beispiel demonstriert die Aneinanderkettung von zwei Aufgaben, wobei ein Abbruch der ersten Aufgabe gleichzeitig die Ausführung der zweiten Aufgabe verhindert. Über die Parent-Eigenschaft werden Aufgabenhierarchien konstruiert. Anders als ein Thread kann eine Aufgabe Werte zurückgeben. Dafür ist die generische Task-Klasse vorgesehen, dessen Typargument den Typ des Ergebnisses spezifiziert (in den Vorabversionen der Parallel Extensions war von der ,,Future-Klasse" die Rede, da das Ergebnis irgendwann in der Zukunft liegt), und deren Result-Eigenschaft das Ergebnis liefert. Die Zugriffe auf die Eigenschaft werden synchronisiert, so dass keine Wait-Anweisung erforderlich ist:Task oTask = Task.Factory.StartNew((iEnd) => { double x = 0.0; for(int i = 1; i < iEnd; i++) x += Math.Log(i); return x; }); //Thread blockiert, bis oTask das Ergebnis liefert Console.WriteLine(oTask.Result); Schleifen parallelisierenDie statischen Methoden For, For< und ForEach< brechen jeden Iterationsschritt auf eine Task-Instanz herunter, weshalb die Aufgaben potentiell parallel ablaufen. Die For-Methode besitzt in ihrer simpelsten Ausprägung drei Parameter. Das erste Argument definiert den Startwert. Dem folgt die exklusive obere Schranke. Die Logik wird mittels eines Delegates deklariert: Parallel.For(1, 5, i => { Console.WriteLine(i); }); Parallele Schleifen müssen mit Bedacht eingesetzt werden. Bei zu simplen Routinen pro Aufgabe ist der Mehraufwand aufgrund der Task-Verwaltung größer als der Nutzen einer Parallelisierung. Bei Schleifen mit Rückgabewerten kommt wiederum die generische Variante ins Spiel. Ursprünglich war eine ForRange-Methode angedacht, die für jeweils einen Iterationsbereich einen Task instanziert. Diese Variante hat es nicht in die Visual Studio 2010 Beta 1 geschafft, allerdings wird es diese Variante wahrscheinlich in der finalen Version des .NET Framework 4.0 geben. Mit den statischen Methoden der Interlocked-Klasse werden atomare Operationen realisiert, um Nebeneffekte wie Race Conditions zu verhindern. AusnahmenEventuelle Ausnahmen während der Aufgabenbearbeitung fängt die Task Parallel Library ab und aggregiert sie. Eine Synchronisierung, etwa durch den Wait-Befehl, fördert die Ausnahmen in Form einer AggregateException zu Tage. Eine AggregateException ist verschachtelt, wenn mehr als eine Ausnahme ausgelöst wurde. DebuggingDas kommende Visual Studio 2010 führt neue Analysefenster ein, durch die Debugging von parallelen Strängen unterstützt wird. Die Fenster Parallel Tasks und Parallel Stacks befinden sich im Menü Debug, wenn die Programmausführung aufgrund eines Breakpoints unterbrochen ist. Das Parallel Tasks-Fenster listet alle Aufgaben, deren Status und dessen Threadzuweisung. Parallel Stacks visualisiert die Threads und zeigt die aktuelle Ausführungsposition an (Abbildung 1). Alternativ fasst diese Auswertung die Stati als Aufgaben zusammen. Abbildung 1: Das Parallel Stacks-Fenster visualisiert die an der Task-Ausführung beteiligten ThreadsIn Zukunft parallelMit dem kommenden .NET 4.0 wird es dank der integrierten Task Parallel Library für Entwickler deutich einfacher, Aufgaben auf mehrere Tasks und damit auf mehrere Kerne zu verteilen. Dank der neuen Klassen im Namespace System.Threading.Tasks müssen sich Entwickler nicht um die Details kümmern, sondern können sich auf das Parallelisieren ihres Codes fokussieren. Die Concurrency Runtime verteilt die Tasks auf Threads und skaliert die Threads entsprechend der vorhandenen Hardware. Die Task Parallel Library wird vor allem bei neuen Projekten zum Einsatz kommen, deren Algorithmen von Anfang an auf die parallele Ausführung ausgerichtet werden. Mittelfristig ist der Einsatz der Task Parallel sehr zu empfehlen, da ihr Einsatz kostensparender ist, als die Entwicklung auf der Basis von Threads, zumal es in naher Zukunft keine allzu großen Sprünge bei der Taktfrequenz der Prozessoren geben dürfte. Jens Konerow ist freier Softwareentwickler, Buchautor und Microsoft Most Valueable Professional (MVP) im Bereich DirectX/ XNA. Er parallelisiert Algorithmen, wo immer ein guter Speedup gewährleistet ist oder die Benutzeroberfläche reaktionsfähig bleiben muss. Peter Monadiemi


Das könnte Sie auch interessieren