Parallelprogrammierung mit Clojure
Die Programmiersprache Clojure wurde primär für nebenläufige Programme entwickelt, ihr Design baut auf dafür wichtigen Konzepten auf. Sich mit diesen auseinanderzusetzen lohnt sich, auch wenn die Sprache noch jung ist.
- Stefan Kamphausen
- Tim Oliver Kaiser
Die Programmiersprache Clojure wurde primär für nebenläufige Programme entwickelt, ihr Design baut auf dafür wichtigen Konzepten auf. Sich mit diesen auseinanderzusetzen lohnt sich, auch wenn die Sprache noch jung ist.
Die hohe Taktfrequenz heutiger Prozessoren erzeugt mittlerweile so viel Abwärme, dass sie nicht mehr effizient voranzutreiben ist. Zeitgemäße Computer erhöhen ihre Gesamtgeschwindigkeit durch den Einsatz von Mehrkernprozessoren. Auf modernen Betriebssystemen arbeiten viele Programme gleichzeitig, und diese lassen sich leicht auf mehrere Prozessorkerne verteilen, wodurch sich der Computer für den Anwender schneller anfühlt.
Clojure – das Buch
Der Artikel ist ein für heise Developer aufbereiter Auszug des "Clojure"-Buchs aus dem dpunkt Verlag, das auf rund 330 Seiten auf die Grundlagen der Sprache, ihre Konzepte zur Bewältigung der Anforderungen paralleler Programmierung und auf die Interaktion mit Java eingeht. Einen schnellen Einstieg in die Grundzüge von Clojure bietet der Artikel "Gelungene Mischung" auf heise Developer.
Damit wird jedoch nur ein Ensemble von Programmen optimiert, ein einzelnes Programm kann von mehreren Kernen in der Regel nicht ohne Anpassungen profitieren. Dazu muss das Programm in Einzelteile, Threads, zerlegt werden, die sich parallel verarbeiten lassen. Multithreading ist kein neues Phänomen, mit steigender Anzahl Kerne gewinnt es jedoch immer weiter an Bedeutung. Jetzt erst laufen die Threads wirklich gleichzeitig ab, was zu konkurrierenden Zugriffen auf geteilte Ressourcen führt.
Dem englischen Begriff "Concurrency" (dt. Nebenläufigkeit) haftet durch seinen Wortstamm die passende Konnotation miteinander um Ressourcen konkurrierender Einheiten an, was den Kern des Concurrent Programming, also der Entwicklung ebensolcher Multithreading-Programme, gut beschreibt. Die Threads eines Prozesses konkurrieren um die Ressourcen: nämlich die Daten, die der Prozess im Speicher vorhält.
Clojure bietet (über die Mechanismen von Java hinaus) kein Werkzeug, um die Veränderungen der Welt außerhalb des eigenen Prozesses – in einem verteilten System – zu verarbeiten; die Sprache konzentriert sich auf lokale Veränderungen im eigenen Prozess, die durch unterschiedliche Threads entstanden sein können.
Zwiefach parallel
Man unterscheidet zwei Arten parallelen Arbeitens. Erstens die Beschleunigung einer langwierigen Verarbeitung, indem Teile einer Berechnung auf verschiedene Prozessoren verteilt werden. Deren Ergebnisse laufen dann an einer zentralen Stelle zusammen und liefern somit das endgĂĽltige Resultat. Ein Job wird durch Verteilung auf Recheneinheiten in seiner Gesamtheit beschleunigt.
Eine zweite Art parallelen Arbeitens betrifft die gleichzeitige Abwicklung mehrerer Aufgaben oder Jobs, die nicht gleichartig sein müssen. Typische Anwendungsfälle sind Webserver, die gleichzeitige Besucher möglichst schnell bedienen müssen, oder aber clientseitige Programme, die in der Oberfläche noch flüssig reagieren sollen, während die Applikation im Hintergrund gerade eine Aufgabe ausführt. Das kann beispielsweise eine komplizierte Berechnung oder eine Kommunikation über das Netz sein. Diese Form der Parallelität gewinnt zusehends an Bedeutung.
Beiden gemein ist, dass sie gelegentlich auf gemeinsame Ressourcen zugreifen mĂĽssen, und das birgt Potenzial fĂĽr viele Konflikte.
Zustand und Zeit
Um die Antworten von Clojure darauf zu verstehen und sie sinnvoll einsetzen zu können, sind einige Sätze zu den Begriffen Zustand, Identität und Wert nötig, die Spracherfinder Rich Hickey auf der Clojure-Webseite "Values and Change – Clojure's approach to Identity and State" ausführlich beschreibt und deren Bedeutung er als Video auf dem InfoQ-Portal überzeugend präsentiert.
Eine Identität ist hier eine logische Einheit, die im Laufe der Zeit verschiedene Zustände einnehmen kann. Ein solcher Zustand ist die Verknüpfung mit einem Wert zu einem bestimmten Zeitpunkt, und ein Wert schließlich ist eine unveränderliche Größe: Die Zahl elf ist die Zahl elf. Sie ist es jetzt, sie war es gestern, und sie wird es morgen auch noch sein. Die Zahl elf ist unveränderlich; sie ist ein Wert.
Bei Zahlen und Namen entspricht diese Anschauung noch der der meisten Programmierer. Es gilt aber, das Konzept auch auf zusammengesetzte Datentypen wie Listen, Vektoren oder Maps auszuweiten: Eine Liste mit den Zahlen eins, zwei und drei wird immer diese Liste bleiben. WĂĽrde eine Vier hinzugefĂĽgt, entstĂĽnde eine neue Liste mit den Zahlen eins, zwei, drei und vier.
Wenn aber die Anzahl der Spieler einer Fußballmannschaft gefragt ist, wird nach einer Identität gefragt, nach etwas mit Bedeutung. Zu einem bestimmten Zeitpunkt entsteht dann eine Verknüpfung zwischen dieser Anzahl und der Zahl elf. Gleichermaßen kann nach der minimal erforderlichen Anzahl der Punkte, um im Tischtennis einen Satz zu gewinnen, gefragt sein, was ebenfalls zu einer Verknüpfung mit der Elf führt. Allerdings hat sich dieser Zustand – die Verknüpfung von Identität und Wert – 2001 geändert. Vorher war der Zustand der Identität "minimal erforderliche Anzahl der Punkte, um im Tischtennis einen Satz zu gewinnen" eine Verknüpfung mit dem Wert 21.
Clojure bietet Werkzeuge, um Veränderungen an Identitäten im Laufe der Zeit zu handhaben.