16
Software-Download: Über 8 GByte Software für Entwickler Intel Parallel Studio XE 2011 Visual Studio 2010 Ultimate Edition Eclipse 3.7.1 für Java-Entwickler Tasktop Dev 2.1.0 Kaazing WebSocket Gateway 31 Episoden des SoftwareArchitekTOUR- Podcasts Sponsored Software: Intel Corp., h. o. Computer Programmieren heute 1/2012 präsentiert von: www.heise-developer.de DEVELOPER Mit Stellenmarkt Gewinnspiel: Notebook zu gewinnen Aktoren, Dataflow, MapReduce, STM & Co.: Konzepte paralleler Programmierung, Multithreading bei Java, .Net und C/C++ Agile Softwareentwicklung & ALM: Continuous Delivery, DVCS, Build, Testing, DevOps, Kanban Zeitgemäße Webentwicklung: Serverside JavaScript, HTML5 und Browser-IDEs IT-Hypes unter der Lupe: Mobile Web Platform as a Service Programmiersprachen-Trends

ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

  • Upload
    others

  • View
    6

  • Download
    0

Embed Size (px)

Citation preview

Page 1: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Software-Download:Über 8 GByte

Software fürEntwicklerIntel Parallel Studio XE 2011Visual Studio 2010

Ultimate Edition Eclipse 3.7.1

für Java-Entwickler Tasktop Dev 2.1.0

Kaazing WebSocket Gateway31 Episoden des SoftwareArchitekTOUR-PodcastsSponsored Software:Intel Corp., h. o. Computer

x D

EVEL

OPE

R1/

2012

Para

llelp

rogr

amm

ieru

ng ●

Agi

le A

LM ●

Prog

ram

mie

rspr

ache

n-Tr

ends

hHeise

Programmieren heute

1/2012

präsen

tiert vo

n:ww

w.heis

e-deve

loper.d

e

DEVELOPER

Mit Ste

llenm

arkt

Gewinnspiel:

Notebook zu

gewinnen

Aktoren, Dataflow, MapReduce, STM & Co.:

Konzepte paralleler Programmierung, Multithreading bei Java, .Net und C/C++

Agile Softwareentwicklung & ALM:

Continuous Delivery, DVCS, Build, Testing, DevOps, Kanban

Zeitgemäße Webentwicklung:

Serverside JavaScript,HTML5 und Browser-IDEs

IT-Hypes unter der Lupe:

Mobile WebPlatform as a ServiceProgrammiersprachen-Trends

xx.1611.001.fuer_epaper.EP 24.11.2011 14:03 Uhr Seite 1

Page 2: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige
Page 3: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

EDITORIAL iX Developer

iX Developer 1/2012 – Programmieren heute 3

Dass das Internet die Art und Weise, wie Menschen leben,denken, arbeiten und ihre Umgebung wahrnehmen, maß-geblich verändert hat, ist eine Binsenweisheit. Davon ammeisten betroffen war und ist die IT-Branche, liegt es dochin ihrer Aufgabe, die Wünsche und Anforderungen webaffi-ner Individuen und Unternehmen zu befriedigen. Das hat zukomplexen Softwarearchitekturen und riesigen Datenmen-gen geführt – und wer glaubt, dass das Datenaufkommenim Internet über die letzten 20ˇJahre weitgehend gleichmä-ßig gestiegen ist, irrt gewaltig. So sahen Experten für dieletzten fünf Jahre eine Datenexplosion um den Faktorˇ10,und das wird sich wohl auch in den nächsten Jahren expo-nentiell weiter vervielfachen.

Zur Bewältigung dieser Aufgabe sind die eingeschlagenenWege verschieden. Mittlerweile gibt es zum Beispiel zahl-reiche Verfahren, den Datentransfer softwareseitig zu be-schleunigen, auf der Hardwareseite sind etwa Multi-Core-und Many-Core-Plattformen en vogue. Laut den Analystenvon IDC sind 2011 bereits die Hälfte der gelieferten Prozes-soren Dual-Core-Systeme; Plattformen mit einem Kern wer-den mit nur noch etwa 10ˇProzent Anteil fast zum Nischen-produkt. Doch schon 2013 sollen die Dual-Cores das gleicheSchicksal erfahren. Die Marktforscher sehen dann 30 bis35ˇProzent Quad-Cores und circa 50ˇProzent für Systememit mehr als vier Kernen.

Heißt das nun, dass sämtliche Software für die neuen Pro-zessorarchitekturen neu oder umzuschreiben ist? Sicherlichnicht. Allerdings sollten sich Entwickler jetzt mit den zahl-reichen Konzepten in der parallelen Programmierung aus -einandersetzen, um abwägen zu können, ob sie aus ihrenneuen oder bestehenden Applikationen dank der Mehrkern-systeme mehr Leistung herausholen können.

Die Konzepte zur parallelen und nebenläufigen Program-mierung – die wichtigsten werden in diesem Heft vorge-stellt – sind größtenteils gar nicht so neu. Sie erfahren jedoch durch die mittlerweile bestehenden Hardwarevor -aussetzungen eine neue, berechtigte Aufmerksamkeit. Ge-wisse Vorteile bei ihrer Implementierung haben sicherlichjunge Sprachen wie Clojure, Scala und Go, die eben zueiner Zeit entstanden sind, als abzusehen war, dass Moore’sLaw zur Verdopplung der Geschwindigkeit der Prozessorenalle 18 bis 24ˇMonate nur noch bedingt greift. Sie werdenteilweise explizit zur Parallelisierung entwickelt. Schwieri-ger haben es hingegen verbreitete Plattformen wie Java undC++, die den Ballast mehrjähriger Entwicklungen mittragenmüssen und dadurch hinsichtlich des Innovationsgrads

behäbig erscheinen. Deswegen dauert es bei ihnen länger,die Konzepte paralleler und nebenläufiger Programmierungzu realisieren. Dafür können Anwender frühzeitig auf eine Vielzahl an Bibliotheken zurückgreifen, die es wieFork/Join bei Javaˇ7 oder Boost.Thread in C++11 endlich indie Standards gepackt haben.

Es ist jedoch nicht allein damit getan, die Beschleunigungnur bei Prozessoren und Programmiersprachen zu suchen.Agile Praktiken tragen ebenfalls zu einer effizienteren undschnelleren Softwareentwicklung bei. Von daher ist es nichtverwunderlich, dass gerade Entwickler von Open-Source-Software versuchen, die mit „Agile“ einhergehenden Prin-zipien in ihren Werkzeugen zu berücksichtigen. Mit Erfolg– wie an den in diesem Heft genannten Agile-ALM-Werk-zeugen zu sehen ist. Sogar so erfolgreich, dass die Herstel-ler kommerzieller Plattformen für das Application LifecycleManagement damit werben, jetzt Open-Source-Versions-kontrollsystemˇX, Open-Source-Build-ToolˇY oder Open-Source-Continuous-Integration-ServerˇZ zu unterstützen.

iX und heise Developer wünschen viel Spaß beim Vertiefenauch in die anderen Trendthemen des dritten iX Developer-Sonderhefts.

ALEXANDER NEUMANN

PS: iX, heise Developer und der dpunkt.verlagbieten im kommenden Mai mit der parallelˇ2012 (www.parallel2012.de) übrigens eine Konferenz zur Parallelprogrammierung an.

Parallel und agil

Page 4: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

ParallelprogrammierungSollen Softwareentwickler die Kunst der Multi-Core-Pro-grammierung erlernen oder gelingt es, die Komplexitätder Parallelisierung hinter der sequenziellen Illusion zuverstecken? Die Community weiß die Antwort (noch)nicht. Darum sollte jeder die grundlegenden Begriffe,Probleme, Lösungsansätze und Paradigmen kennen.

ab Seite 6

Agile ALMVor allem durch den Erfolg von Open-Source-Softwareund die Verbreitung agiler Praktiken hat sich die Land-schaft des Application Lifecycle Management in denletzten Jahren grundlegend verändert. Trotz der damiteinhergehenden Innovation bleibt die große Heraus-forderung die Integration zwischen den Tools.

ab Seite 46

ParallelprogrammierungMulti-Core-ProgrammierungGrundsätzliches zur nebenläufigen und parallelen Programmierung 6

STMSoftware Transactional Memory in Clojure 16

AktorenAktor-Programmierung am Beispiel von Akka 21

MapReduceNebenläufigkeit in Googles Programmiersprache Go 26

DataflowNebenläufigkeit am Beispiel von Groovy 31

MultithreadingConcurrency-Bibliotheken bei .Net und Java 34

Thread-ProgrammierungNebenläufige Programmstrukturen in C++11 40

Agile ALMApplication LifecyleVon Open Source zu Enterprise: Das neue Gesicht von ALM 46

VersionskontrolleÜber den Status quo verteilter Versionsverwaltungssysteme 51

Build-ToolsSBT und Gradle: Aufstrebende Konkurrenz für Ant und Maven 57

Continuous IntegrationKontinuierliches Integrieren in Zeiten agiler Programmierung 66

Build-ManagementPünktliche Releases mit Continuous Delivery 71

QualitätssicherungAuswertung der Umfrage „Softwaretest in der Praxis“ 74

SecuritySoftwaresicherheit durch Application Lifecycle Management 78

RechtVertragliche Tücken der agilen Softwareentwicklung 83

WebentwicklungHTML5 IBedeutung und Einsatz von HTML5 in der Praxis 86

HTML5 IIWebSocket: Annäherung an Echtzeit im Web 91

ToolsBrowser-Entwicklungsumgebungen im Vergleich 95

4 iX Developer 1/2012 – Programmieren heute

INHALT iX Developer

xx.1611.004-005.fuer_epaper.EP 24.11.2011 14:04 Uhr Seite 4

© Copyright by Heise Zeitschriften Verlag

Page 5: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Software-DownloadIntel Parallel Studio XE 2011: Die Toolsuite für dieParallelprogrammierung kombiniert Intels C++- und Fortran-Compiler, Performance- und Parallel-Bibliotheken mit denProfiling-Werkzeugen Intel Inspector XE und Intel VTuneAmplifyer XE (30-tägige Testversion).Eclipse 3.7.1 für Java-Entwickler: Das erste Service Pack deraktuellen Version der Java-Entwicklungsumgebung berücksichtigtdie mit Javaˇ7 eingeführten neuen Sprach-Features.Visual Studio 2010 Ultimate Edition: Die Entwicklungsumgebungfür das Application Lifecycle Development bietet Teams einumfassendes Paket von Verwaltungstools für den gesamtenLebenszyklus von Anwendungen (30-tägige Testversion).Tasktop Dev 2.1.0: bietet eine Integrationvon ALM-Tools innerhalb von Eclipse undVisual Studio (30-tägige Testversion).Kaazing WebSocket Gateway:ein Web Socket-Server, derverschiedene Protokolle und Client SDKs anbietet.31 Episoden desSoftwareArchitekTOUR-Podcasts

Käufer der digitalen Ausgabe finden hierein Image der Heft-DVD zum Herunterladen:ftp://ftp.heise.de/pub/ix/developer/0112/dvd.iso

WebentwicklungDynamische Webapplikationen vollgepackt mit Mobileund Echtzeit setzen neue Techniken voraus. Webent-wickler müssen sich daher mit den damit einhergehen-den Konzepten vertraut machen. Das Wichtigste überdie neue Webwelt aus HTML5, Serverside JavaScript,Browser-IDEs und mobilen Webframeworks

ab Seite 86

SkriptspracheObjektorientiert, integriert, effizient entwickeln in JavaScript 100

Serverside JavaScriptProgrammierung mit Node.js 106

Mobile PlattformenAktuelle UI- und Hybrid-Frameworks für mobile Webapplikationen 111

TrendsProgrammiersprachenAktuelles zur Entwicklung von Programmiersprachen 117

Domain Specific LanguagesInterne DSLs: Programmiersprachen in Programmiersprachen einbetten 122

SoftwarearchitekturEvent Based Components: Softwarearchitektur in neuem Licht 126

Big DataDatenverarbeitung im Zeitalter des Web 2.0 130

RADRenaissance des Rapid Application Development 136

PaaSPlatform as a Service – eine Übersicht der Angebote 139

MethodenDevOpsNeuartige Zusammenarbeit zwischen Softwareentwicklung und Betrieb 144

KanbanSoftware-Kanban im Einsatz 149

WeiterbildungBessere Softwareentwickler dank Coding-Dojos und Code-Katas 153

SonstigesEditorial 3

Gewinnspiel 8

Stellenmarkt 62

Inserentenverzeichnis 121

Impressum 121

Artikel mit Verweisen ins Web enthalten am Ende einen Hinweis darauf, dass

diese Webadressen auf dem Server der iX abrufbar sind. Dazu gibt manden iX-Link in der URL-Zeile des Browsers ein. Dann kann man auch dielängsten Links bequem mit einem Klick ansteuern. Alternativ steht obenrechts auf der iX-Homepage ein Eingabefeld zur Verfügung.

5iX Developer 1/2012 – Programmieren heute

Alle Links: www.ix.de/ix1116SSS

xx.1611.004-005.fuer_epaper.EP 24.11.2011 14:04 Uhr Seite 5

© Copyright by Heise Zeitschriften Verlag

Page 6: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Es ist zum gängigen Klischee geworden, dass Softwareent-wickler ihren Programmierstil grundlegend ändern müs-

sen, wenn ihre Programme künftig die verfügbaren Compu-ter auch nur annähernd in Anspruch nehmen sollen. AlsGrund hierfür wird seit etwa fünf Jahren die veränderte Dy-namik der Hardwareentwicklung angeführt. Während vorherMoore’s Law dazu führte, dass sich alle 18 bis 24ˇMonatedie Geschwindigkeit der Prozessoren verdoppelte, stagniertseitdem die Taktfrequenz. Stattdessen wächst die Anzahl derRechenkerne (Cores) in ähnlichem Maße. Heute sind Rech-ner mit vier oder acht Cores, in absehbarer Zeit – so die Ver-mutung – mit 64 oder gar 1024ˇKernen zu kaufen.

Die verbreiteten Programmiersprachen und -paradigmengehen überwiegend von sequenziellen Abläufen aus und las-

sen die verfügbare parallele Rechenpower außen vor. Wasmuss sich ändern, damit Entwickler auch in Zukunft Soft-ware schreiben können, deren zunehmende Komplexitätnicht zu längeren Laufzeiten führt, sondern die dann vorhan-dene Rechenleistung effektiv zum Einsatz bringt.

Sollten alle Softwareentwickler die Kunst der „Multi-Core-Programmierung“ erlernen? Oder wird es den Frame-work-Gurus gelingen, die Komplexität der Parallelisierungso zu verstecken, dass der gemeine Anwendungsentwicklerauch weiterhin in seiner sequenziellen Illusion leben kann?Die Software-Community kennt die Antwort (noch) nicht.Darum sollte jeder, der mitreden möchte, die grundlegen-den Begriffe, Probleme, Lösungsansätze und Paradigmenkennen.

Verwandt, aber nicht verheiratetEine kleine Begriffsverwirrung ist zu Beginn zu klären: ne-benläufig ist nicht gleich parallel. Während „parallele“ Pro-grammausführung auf das Vorhandensein mehrerer gleich-zeitig arbeitender Recheneinheiten angewiesen ist, steckthinter dem Begriff „Nebenläufigkeit“ (Concurrency) die abs-trakte Idee, dass mehrere Aufgaben (Tasks) quasi neben -einander bestehen und Fortschritte machen können. Das kanndurch tatsächliche Parallelität geschehen, aber auch durchhäufiges Wechseln zwischen mehreren Aufgaben (Task Swit-ching), wie es seit Jahrzehnten Time-Slicing- und Multi-Thread-Systeme praktizieren.

6 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG Multi-Core-Programmierung

KernschwemmeJohannes Link

Gängige Programmiersprachen und -paradigmen setzen zumeist sequenzielle Abläufe um und nutzen nicht die parallele Rechenpower heutiger Prozessorarchitekturen.Was muss sich also ändern, damit EntwicklerSoftware schreiben können, die aufgrund zunehmender Anforderungen nicht zu längeren Laufzeiten führt, sondern die Rechenleistung effektiv nutzt?

Grundsätzliches zur nebenläufigen und parallelen Programmierung

Page 7: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Nebenläufigkeit ist somit mehr als parallele Programm-ausführung. Sie erlaubt es beispielsweise, dass Entwickler aneinem Text weiterschreiben können, während dieser „gleich-zeitig“ auf der Festplatte gespeichert wird. Und sie ermög-licht es, dass im Browser der Anfang einer Seite zu sehen ist,während im Hintergrund noch der Rest aus dem Netz gela-den wird. Nebenläufigkeit ist damit für jeden Entwickler in-teressant, auch wenn er Programme für einen einzigen Re-chenkern entwickelt.

Parallele und nebenläufige Programmierung teilen sich je-doch einen großen Teil der Techniken, Abstraktionen undProbleme. Beide Bereiche lassen sich mit Multi-Threadingund Shared-Memory angehen – und Entwickler handeln sichdamit bei beiden ähnliche Probleme ein. Doch nicht immermuss Nebenläufigkeit auf dem gleichen Prinzip wie Paral-lelität beruhen. Als Gegenbeispiel soll die Idee der „non-blocking IO“ dienen. Hierbei warten nicht etwa mehrereThreads gleichzeitig darauf, dass ihre jeweilige I/O-Res-source für das Schreiben beziehungsweise Lesen bereitsteht,sondern ein einziger Thread fragt in einer Schleife ständigalle interessanten I/O-Quellen nach ihrer Bereitschaft für Le-se- und Schreibzugriffe ab. Erklärt sich eine Quelle als be-reit, wird umgehend erledigt, was auch immer zu lesen, zuschreiben oder zu verarbeiten ist. Der Ansatz ist oft spür-bar per formanter als Thread-basiertes Warten und skaliertüber die Zahl verfügbarer Threads hinaus. Daher kommt erimmer dort zum Einsatz, wo es auf maximalen Daten-durchsatz ankommt. Microsoft hat mit seiner Async-Biblio-thek nebenläufige I/O gar zum festen Bestandteil der .Net-Plattform gemachtˇ[a]. Nicht blockierende, asynchrone I/Oist somit eine Technik der Nebenläufigkeit, jedoch nicht derparallelen Programmierung.

Unabhängig davon, ob man nun Nebenläufigkeit oderParallelität beobachtet, gibt es einige Herausforderungen zumeistern:• Zerlegung:ˇUm überhaupt nebenläufige Programmausfüh-

rung einsetzen zu können, ist ein sequenzieller Algorith-mus in mehrere, möglichst unabhängige Teilaufgaben zuzerlegen. Das liegt manchmal auf der Hand (z.ˇB. dasasynchrone Lesen einer Datei), ab und an erfordert dieZerlegung eine vollständig andere Herangehensweise anein Problem. „Parallele Algorithmen“ stellen ein eigenesForschungsgebiet dar, oft kommt man aber mit einfachenHilfsmitteln recht weit. Dazu später mehr.

• Koordination:ˇWenn bekannt ist, in welche Teilaufgabenein Problem zerlegt wird, sind diese Aufgaben anzustoßenund – meist – am Ende auch wieder zusammenzuführen.Im einfachsten Fall wartet der Entwickler auf das Ende al-ler Teile, oft muss er jedoch die Teilergebnisse in ein Ge-samtergebnis einbringen.

• Kommunikation und Synchronisation:ˇWenn Teilaufgabennicht vollständig voneinander unabhängig sind, bedarf eswährend ihrer Ausführung noch einer Abstimmung. Daskann durch Zugriff auf gemeinsamen Speicher geschehen(„Shared Mutable State“, siehe unten) oder durch denAustausch von Nachrichten. Die Kommunikation undSynchronisation ist es, die zu typischen Problemen wieRace Conditions und Deadlocks führt.

Diese Herausforderungen gehen diverse Nebenläufigkeitsan-sätze völlig unterschiedlich an.

Die oft einfachste Variante der Nebenläufigkeit ist derStart eines neuen Prozesses. Unter Unix-kompatiblem Sys-tem erreicht der Entwickler das beispielsweise, indem er aufder Kommandozeile eine Anweisung mit „&“ abschließt. Da-

mit startet er ein Kommando oder ein Programm als eigenenProzess im Hintergrund. Da ein Prozess viele Betriebssys-temressourcen benötigt, eignet sich dieses Verfahren fürgrobgranulare Nebenläufigkeit. Zudem sind die Kommuni-kationsmechanismen zwischen Prozessen relativ aufwendigund langsam; außer Pipes existiert in der Regel nur die Im-plementierung eines eigenen Protokolls über TCP-Socketsoder gar HTTP.

Der Vorteil von Parallelisierung mit Prozessen bestehtvor allem darin, dass die einzelnen Programme intern nichtfür nebenläufige Verwendung ausgelegt sein müssen. Zu-dem kann man selbstständige Prozesse beinahe ebensoleicht auf einem anderen Rechenknoten starten wie auf demlokalen Rechner; die Fähigkeit zum Scaling Out bekommtman quasi umsonst dazu.

Einfache Nebenläufigkeit mit ThreadsBenötigt ein Entwickler Nebenläufigkeit innerhalb dessel-ben Prozesses, etwa weil er als Reaktion auf ein GUI-Eventeine lang laufende Aktion, beispielsweise einen Dateiexport,starten, aber trotzdem dem Nutzer die Weiterarbeit erlaubenwill, kommen fast automatisch Threads ins Spiel. Sie sindim Vergleich zu Prozessen hinsichtlich ihres Speicherver-brauchs leichtgewichtig, darüber hinaus können sie gemein-sam auf denselben Speicherraum zugreifen – Segen undFluch gleichermaßen.

Jede ernst zu nehmende Programmiersprache bietet heut-zutage Konstrukte an, um eine Aktion in einem neuen Threadzu starten. In Groovy (siehe Kasten „Warum Groovy-Code-beispiele?“) sieht das wie folgt aus:

iX Developer 1/2012 – Programmieren heute 7

Warum Groovy-Codebeispiele?Die meisten Beispiele im Artikel sind in Groovy geschrieben. Dashat zwei Gründe: Groovy erlaubt sehr kompakten und dennochlesbaren Code. Zudem ist die Groovy-Basissyntax für einen Java-oder C#-Entwickler leicht zu verstehen. Ein paar Dinge sollte manjedoch wissen, um die Beispiele verstehen zu können:

• Jegliche Typinformation ist in Groovy optional. Bei der De-klaration von Variablen oder Methoden ersetzt man den Typdurch das Keyword def, in den meisten anderen Fällen kannman den Typ einfach weglassen. Einige Beispiele:

def alter = 13 def addiere(a, b) {

return a + b }

• Klammern können manchmal weggelassen werden. addiere(a,b) lässt sich daher auch addiere a, b schreiben.

• Groovy erlaubt das Erzeugen anonymer Methoden, soge-nannter Closures. Sie entsprechen den Lambda-Ausdrückenin C#, in Java kommen anonyme innere Klassen eines Inter-face mit einer Methode der Idee von Closures am nächsten.Closures lassen sich genau wie andere Objekte als Parameterund Rückgabewerte verwenden. Zwei Beispiele:

def aktion = {println "Ich bin eine Closure"}aktion()def iterator = {zahl -> println "Die Zahl heißt $zahl"}(1..10).each(iterator)

Auf esoterische Sprachfeatures verzichtet der Autor weitgehend –oder er erklärt diese an Ort und Stelle.

Page 8: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Thread.start { println "Ich laufe nebenläufig" }println "Ich nicht"Der Code startet einen eigenen Thread, der “Ich laufe ne-benläufig“ auf der Konsole ausgibt. Ob die Zeile oder derText “Ich nicht“ zuerst erscheint, kann je nach Timing undAusführungsumgebung variieren. Würde die Laufzeitumge-bung nicht explizit dafür sorgen, dass println (= java.lang.System.out.println) bei der Ausgabe einer Zeile nicht unter-brochen wird, ließen sich gar die einzelnen Buchstaben derbeiden Texte vermischen. Damit hat der Leser bereits einewichtige Eigenschaft nebenläufiger Programme kennen ge-lernt: Ohne zusätzliche Synchronisation kann man nichtwissen, in welcher Reihenfolge der Code unterschiedlicherThreads zur Ausführung kommt. Ein nebenläufiges Pro-gramm ist daher inhärent nichtdeterministisch. Ob derNichtdeterminismus zum Problem wird oder nicht, hängtvon vielen Randbedingungen ab; dazu später mehr.

Die etwas leichteren Threads: TasksHeutige Betriebssysteme unterstützen Threads nativ. Siestellen unter anderem Mittel bereit, mit denen sich Threadsuntereinander synchronisieren lassen. Es kann auch durch-aus passieren, dass mehrere Threads gleichzeitig blockiertsind – weil sie auf ein I/O-Ereignis oder ein anderes Eventwarten; in dieser Zeit beanspruchen sie keine Rechenzeit.Sobald das Event eintritt, sorgt das Betriebssystem dafür,dass der jeweilige Thread wieder Rechenzeit erhält.

Threads sind auch aus anderen Gründen nicht das idealeMittel, um einzelne Codestücke zu parallelisieren: Sie ver-

brauchen immer noch recht viel Ressourcen (eigener Stackplus Verwaltungsdaten), ein Thread-Wechsel auf dem glei-chen Rechenkern dauert relativ lange und die Anzahl nativerThreads ist auf allen gängigen Betriebssystemen beschränkt(meist auf wenige Tausend). Möchte der Entwickler im ei-genen Programm daher eine große Anzahl von Aufgabenparallelisieren, muss er auf andere Mittel zurückgreifen:Task Parallelism. Ein Task ist eine kleine Aufgabe, derenAusführung zeitlich unabhängig von anderen Tasks statt-finden kann.

In Java – und damit auch in Groovy – steht der Executor-Service für diesen Zweck zur Verfügung, in C# erledigt derTaskScheduler Ähnliches. Beide nehmen Aufträge zum Aus-führen der Tasks entgegen. Im Hintergrund arbeitet ein Poolvon Threads, in dem die einzelnen Tasks (in Java durch dasInterface Callable repräsentiert) meist in der Reihenfolge ihrer„Beauftragung“ abgearbeitet werden. Liefert eine solche Auf-gabe einen Rückgabewert, kann man sich diesen in einem Fu-ture-Objekt zurückgeben lassen. Der Name legt es nahe: Ein„Zukunftsobjekt“ liefert den Rückgabewert erst, wenn man da-nach fragt – future.get() –, und blockiert die Ausführung, fallsdie Berechnung zu dem Zeitpunkt noch nicht beendet ist.

Das folgende Beispiel berechnet das Quadrat der Zahlen 1bis 10, indem es jede einzelne Berechnung an einen Thread-Pool übergibt:

ExecutorService executorPool = Executors.newFixedThreadPool(4);(1..10).each { Integer zahl ->

Callable task = new Callable() { def call() {zahl * zahl} }Future quadrat = executorPool.submit(task)println quadrat.get()

}

8 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG Multi-Core-Programmierung

Intel und h.o. Computer präsentieren:

Die Gewinner werden per Losentscheid unter allen Einsendern mit der richtigen Antwort ermittelt. Mitarbeiter von h.o. Computer, Intel und dem Heise Zeitschriften Verlagsowie deren Angehörige sind von der Teilnahme ausgeschlossen. Der Rechtsweg ist ausgeschlossen.

1× NotebookLenovo IdeaPad S10 T3

4× Huawei U8110Android-Smartphone mit WLAN,

UMTS/HSPDA, GPS

5× Intel Parallel Studio XE für WindowsSingle-User-Lizenzen

5× AbonnementEin-Jahres-Abonnement von iX

Beantworten Sie einfach bis 1. März 2012 unter ix.hocomputer.de die folgende Frage:

Wie heißt Intels populäre C++-Bibliothek zur effizienten Ausnutzung von Multi-Core-Prozessoren?

a) ShareLib b) Threading Building Blocks c) C++Pars

Mitmachen lohnt sich, denn auf Sie warten attraktive Preise!

Gewinnspiel zur ParallelprogrammierungGewinnspiel zur Parallelprogrammierung

Page 9: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Größe und andere Eigenschaften des Pools sind konfigurier-bar; die Wahl der optimalen Parameter hängt von zahlreichenBedingungen ab, unter anderem von der Zahl der verfügba-ren Rechenkerne und vom Aufgabentyp. Führt der Entwick-ler CPU-intensive Berechnungen durch, sollte er etwa so vie-le Threads nutzen, wie er Rechenkerne zur Verfügung hat.Bei blockierenden Zugriffen auf externe Ressourcen (Netz-werk, Dateien, Grafikkarte) erlauben mehr Threads häufigschnellere Antwortzeiten.

Auch Tasks greifen, da sie unter der Haube Threads be-nutzen, auf einen gemeinsamen Hauptspeicherbereich zu undtreffen daher auf ähnliche Synchronisationsprobleme. Hinzukommt, dass derzeitige Betriebssysteme Tasks noch nicht na-tiv unterstützen, sondern diese in externen Bibliotheken im-plementieren. Kombiniert man daher Task-Parallelismus mitThread-Synchronisation, kann es zu folgender Situationkommen: Zwei Lese- warten auf einen dritten Schreib-Task;wenn die beiden Lese-Tasks jedoch ihre Threads blockierenund der Thread-Pool lediglich zwei Threads enthält, dannwarten sie für immer. Task-Parallelismus benötigt aus demGrund eigene Mechanismen zur gegenseitigen Synchronisa-tion und kommt insbesondere mit blockierender, synchronerI/O nur schlecht zurecht.

Das Problem könnte sich mit der nativen Integration vonTask-Parallelismus ins Betriebssystem verbessern. ApplesOSˇX hat mit Grand Central Dispatchˇ[b] einen Anfang ge-macht, den die Konkurrenz derzeit aufgreift.

Shared Mutable State: Die Nebenläufigkeit wird zum ProblemDie meisten Programme, die von sich behaupten, „parallel“oder gar „Multi-Core-fähig“ zu sein, benutzen den gleichenKommunikationsmechanismus: Die einzelnen Threads oderTasks greifen auf einen gemeinsamen Zustand (Shared State)in Objekten zu und sorgen (meist) mit Locks dafür, dass siesich trotz gleichzeitiger Ausführung nicht in die Quere kom-men. Ist der Programmierer beim Einsatz – oder beim Vermei-den – der Locks nur geschickt genug, erreicht er die dreiHauptziele nebenläufiger Programmierung: Safety, Livenessund Performance. Das sei an folgendem Beispiel verdeutlicht.

Das fachliche Problem ist einfach: In einem Regal (Shelf)kann man Produkte (Product) einlagern (putIn) und wiederhervorholen (takeOut). Das Regal hat jedoch nur eine be-stimmte Kapazität; ist das Regal voll, wird beim Hineinle-gen eine StorageException geworfen. Das Lager (Store -house) ist eine Sammlung von Regalen und erlaubt nebendem Aufstellen neuer Regale (newShelf) das Bewegen vonProdukten aus einem Regal in ein anderes. Alle Operationensollen in parallelen Threads sicher ausführbar sein. Betrach-tet sei zunächst die Shelf-Klasse (Listingˇ1).

Sowohl die lesenden Zugriffe (isEmpty, isFull) als auchdie verändernden Operationen (putIn, takeOut) sind durch ei-nen Lock zu schützen, um Race Conditions auszuschließenbeziehungsweise um für die Sichtbarkeit veränderter Werteüber den lokalen Thread hinaus zu sorgen. lock {} ist ein Kon-strukt, das Groovy durch ein wenig Metaprogramming imNachhinein hinzugefügt wurde [8]. Es führt im Hintergrundnicht nur lock.lock() und lock.unlock() aus, sondern sorgt auchim Falle einer Exception für die korrekte Freigabe des Lock.Race Condition nennt man den Fall, wenn es bei einer be-stimmten nebenläufigen Konstellation – ein Thread überholtbeim Rennen einen anderen – zu Inkonsistenzen in der Fach-

logik kommen kann. Bei einer nicht atomaren oder durchLocks geschützten Put-in-Operation könnte zwischen der is-Full-Überprüfung und dem tatsächlichen Einfügen des Pro-dukts ein anderer Thread bereits das Regal füllen. Das hättezur Folge, dass das Regal auf einmal mehr Produkte enthält,als sein Fassungsvermögen zulässt.

Dem Zweck der Thread-übergreifenden Sichtbarkeit dientauch der final-Modifier an den öffentlichen Members – inGroovy properties genannt. Was zunächst wie eine Eigenartdes Java-Memory-Modells aussieht, findet man in veränder-ter Form auf allen anderen Plattformen wieder. Es ist dafürSorge zu tragen, dass Veränderungen im lokalen Cache ei-nes Thread auf den „echten“ Hauptspeicher übertragen wer-den. Auf Prozessorebene spricht man oft von Speicherbarrie-ren (Memory Barriers), die in den Code an passender Stelleeinzufügen sind; sowohl Locks als auch das final-Schlüs-selwort sorgen im Beispiel dafür. Das Problem liegt unteranderem darin, dass Speicherbarrieren und Locks das Pro-gramm potenziell verlangsamen; der Zugriff auf den Haupt-speicher benötigt ein bis zwei Größenordnungen mehr Rech-enzyklen als der Zugriff auf einen Level-1-Cache des geradeaktiven Rechenkerns. Nichts ist umsonst.

Betrachtet sei nun die Storehouse-Implementierung (sieheListingˇ2). Zwei Dinge fallen auf. Es wurde shelves als Con-currentHashMap erzeugt; die Verwendung eines Datentyps,der inhärent Thread-sicher ist, ermöglicht den Verzicht aufzusätzliches Locking in der getAt-Methode. Thread-sichereDatentypen gibt es für alle gängigen Plattformen, und mansollte sich mit ihnen vertraut machen (siehe auch den Arti-kel auf Seite 34). Häufig sind diese ohne blockierende Syn-chronisation implementiert und haben daher ein recht gutesLaufzeitverhalten.

Um jedoch move so zu implementieren, dass ein Entwick-ler nicht in die oben beschriebene Race Condition gerät,müsste er auf die Lock-Objekte der darunterliegenden Shelf-Instanzen zugreifen. Dieser Doppel-Lock birgt jedoch dieGefahr einer Verklemmung (Dead Lock). Treffen beispiels-

iX Developer 1/2012 – Programmieren heute 9

class Shelf {final int capacityfinal private products = [] // Erzeugt unsynchronisierte

// leere Listefinal lock = new ReentrantLock()Shelf(capacity) {

this.@capacity = capacity}List getProducts() {

lock {return this.@products

}} void putIn(Product product) {

lock {if (isFull())

throw new StorageException("shelf is full.")products << product

}}boolean takeOut(Product product) {

lock {return products.remove(product)

}}boolean isEmpty() {

lock {return products.isEmpty()

}}boolean isFull() {

lock {return products.size() == capacity

}}

}

Listing 1: Shelf mit expliziten Locks

Page 10: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

weise zwei Threads aufeinander, bei dem der eine Produktevon Regal A zu Regal B bewegt und der andere in die entge-gengesetzte Richtung, dann kann es passieren, dass der ersteThread den Lock für Regal A sichert, während gleichzeitigder zweite Thread den Lock für Regal B ergattert. Von nunan müssen beide bis in alle Ewigkeit auf den Lock für dasjeweils andere Regal warten. Diesem klassischen Lock-Or-dering-Problem lässt sich abhelfen, indem man bei allenMehrfach-Locks des Programms für eine eindeutige, deter-ministische Lock-Reihenfolge sorgt. Etwa so:

class Storehouse...boolean move(Product product, String from, String to) {

final locks = [shelves[from].lock, shelves[to].lock].sort {System.identityHashCode(it)}

locks[0] { locks[1] {

if (!shelves[to].isFull() && shelves[from].takeOut(product)) {shelves[to].putIn(product)return true

}return false

}}

}

Das war harte Arbeit. Trotz der leicht verständlichen Fachlo-gik musste einiges an Kontextwissen bemüht werden, umThread-Sicherheit zu erreichen, Verklemmungen zu vermei-den und dennoch die parallele Ausführbarkeit der Fachlogikzu erhalten. Dabei kann es passieren, dass schon die nächstefachliche motivierte Änderung zusätzlichen Synchronisati-onsaufwand erfordert oder gar zum Wechsel der Synchroni-sationsstrategie zwingt. Viele Entwickler, die sich näher mitdem Thema beschäftigen – zum Beispiel indem sie das Buchvon Brian Goetz [1] lesen –, kommen daher zur Einsicht,dass die systemweite, korrekte Verwendung von Locking alsHauptmechanismus für Thread-sichere Programmierung ihrepersönlichen Fähigkeiten sprengt. Wer noch zweifelt, mögesich an der Thread-sicheren Variante der in [2] beschriebe-nen Observer-Klasse versuchen.

Das Problem mit dem LockingDer Hauptgrund für die Schwierigkeiten beim vorgestelltenProgrammieransatz ist die mangelnde KomponierbarkeitThread-sicherer Objekte der gezeigten Art: Nur weil ein Ob-

jekt – Shelf – für sich alleine betrachtet Thread-sicher ist,lässt es sich nicht einfach in einem anderen Objekt – Store-house – benutzen und kann dessen Thread-Sicherheit „er-ben“. Der Entwickler muss über den Synchronisationsme-chanismus von Shelf Bescheid wissen und darüber hinaus dasWissen über die Gesamtstrategie, die geordnete Lock-Rei-henfolge, im Rest des Systems verteilen. Brian Goetzˇ[3]nennt das Kind beim Namen: „Locking is not composable.“Damit bricht die wunderbare Abstraktion vom gekapseltenObjekt, das man zu anderen komplexeren Objekten zusam-menbauen kann, ohne sich über dessen Implementierung Ge-danken zu machen, beim Einsatz von Locks zusammen. Wasist nun zu tun?

Isolierte Nebenläufigkeit In vielen Fällen hilft die konsequente Anwendung einesgrundlegenden Softwaredesignprinzips: Separation of Con-cern. Ebenso wie man jedem anderen fachlichen und techni-schen Aspekt seine eigene Komponente spendiert, macht mandas für seine nebenläufige Aspekte. So wird nicht einfach einneuer Thread oder Task innerhalb der Fachlogik abgespalten,sondern der Entwickler delegiert die Task-Erzeugung bei-spielsweise an ein Dispatcher-Objekt. Ein solcher Dispatcherist für sich genommen einfach zu implementieren und erlaubtdem Programmierer darüber hinaus, für Unit-Tests auf se-quenzielles, deterministisches Verhalten zu wechseln.

In ähnlicher Form muss er den Zugriff auf Shared Statenicht immer im gesamten System verteilen, sondern kannihn in einem oder wenigen Modulen verstecken. Auf dieThread-Sicherheit dieser Module muss der Entwickler dannerhöhte Aufmerksamkeit verwenden, der Rest seiner Soft-ware bleibt „sequenziell einfach“. Venkat Subramaniam be-schreibt die Idee ausführlichˇ[4].

Erreicht man mit dieser Vereinfachungsstrategie nicht dasZiel – das verbesserte Antwortverhalten der GUI oder dieBeschleunigung einer Berechnung –, lohnt sich der Blick aufProgrammierparadigmen, die dem eingesessenen OO-Pro-grammierer ungewohnt erscheinen. Zum Großteil sind siealtbekannt, fast vergessen und erleben nun im Multi-Core-Zeitalter ihre Renaissance.

Transaktionaler SpeicherEin denkbarer Ausweg aus dem Dilemma der fehlendenKomponierbarkeit ergibt sich, wenn man eine zentrale Ideeaus der Welt der Datenbanken auf den Hauptspeicher über-trägt: der Heap wird zur transaktionalen Datenmenge. Manerlaubt die Deklaration einer atomaren Codesequenz, derenAusführung ähnliche Charakteristika haben soll wie eineACID-Datenbanktransaktion:• Atomic:ˇEntweder tritt jede Zustandsänderung in Kraft

oder keine.• Consistent:ˇWar der Zustand vor der Ausführung konsis-

tent, ist er es danach auch.• Isolated:ˇDie Auswirkungen der Codesequenz auf den

Programmzustand bekommen andere Threads erst nacherfolgreichem Abschluss zu Gesicht.

• Durable:ˇDauerhaft im Sinne einer Datenbank sind ato-mare Operationen im Hauptspeicher nicht; dafür solltensie für alle Threads vollständig sichtbar sein (safely pu-blished).

10 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG Multi-Core-Programmierung

class Storehouse {final shelves = [:] as ConcurrentHashMapShelf newShelf(String name, int size) {

shelves[name] = new Shelf(size)}Shelf getAt(String name) {

return shelves[name]}boolean move(Product product, String from, String to) {

def lockFrom = shelves[from].lockdef lockTo = shelves[to].lockreturn lockFrom {

lockTo {if (!shelves[to].isFull() && shelves[from].take

Out(product)) {shelves[to].putIn(product)return true

}return false

}}

}}

Listing 2: Storehouse mit Deadlock-Gefahr

Page 11: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige
Page 12: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Ergänzt der Leser diese Eigenschaften um Schachtelbarkeit,optimistisches Rollback und automatische Wiederholung deratomaren Sequenz im Falle einer Kollision, erhält er ein in-tuitives Programmiermodell, das Verklemmungen ausschließtund spürbar einfacher zu verwenden ist als explizite Locks.Listingˇ3 zeigt einen denkbaren Ausschnitt Shelf und Store-house in Pseudo-Groovy – man beachte das neue Schlüssel-wort atomic.

Der entscheidende Unterschied zur Locking-Lösung ist,dass das Storehouse-Objekt nichts darüber wissen muss, wieSynchronisation in den verwendeten Shelf-Instanzen funk-tioniert. Der Entwickler gewinnt dadurch die Komponierbar-keit der lieb gewonnenen Objekte zurück. Doch leider be-kommt er die Bequemlichkeit nicht geschenkt. MehrereNachteile transaktionalen Speichers lassen sich aufzählen:• Der Ansatz bietet keine Hilfestellung bei der Parallelisie-

rung der Algorithmen und Programme.• Innerhalb atomarer Codeteile dürfen keinerlei Seitenef-

fekte (z.ˇB. Schreiben in Datenbank oder Dateisystem,GUI-Ausgabe) auftreten, da man nicht weiß, wie oft derCode einer Transaktion auszuführen ist, bevor diese sicherfolgreich beenden lassen.

• Verklemmungen sind zwar ausgeschlossen, Fortschritte imProgrammfluss ohne Fortschritte in der Programmlogik,sogenannte „live locks“, sind jedoch immer noch möglich.

Dann ist da noch die Frage der Praxistauglichkeit. Hardware-basiertes TM (Transactional Memory) existiert bislang nurin den Forschungslabors der Chip-Hersteller. Effiziente Im-plementierungen von STM (Software Transactional Memo-ry) sind seit geraumer Zeit Forschungsthema, was die Ver-mutung nahelegt, dass es sich um ein nicht triviales Problemhandelt. Insbesondere ist der Versuch, jede Variablenver-wendung in die Transaktion einzubeziehen, äußerst schwie-rig. Aus dem Grund gehen die meisten in der Praxis ver-wendeten STM-Systeme einen anderen Weg: VeränderlicheReferenzen sind explizit zu kennzeichnen und dürfen nurinnerhalb von Transaktionen geändert werden. Packt mannun noch unveränderliche Datentypen hinzu (siehe nächsterAbschnitt), ergibt sich ein praxistaugliches Programmier-modell, das sich allerdings grundlegend vom gewohntenOO-Vorgehen unterscheidet. Der Lisp-Dialekt für die JVM,Clojure, ist der zurzeit bekannteste Vertreter dieser STM-Spielart. Mehr über STM bei Clojureˇ[c] kann man im Arti-kel auf Seiteˇ16 lesen.

Immutable State: Alles wird einfacherLocking und andere Synchronisationskonzepte benötigt derEntwickler, weil er von unterschiedlichen Threads aus aufgemeinsamen veränderlichen Zustand zugreifen will. Wa-rum also nicht einfach auf veränderlichen Zustand völligverzichten und damit zahlreiche Nebenläufigkeitsproblememit einem Schlag aus dem Weg räumen? Diese Philosophieder Immutability, die die meisten funktionalen Sprachen als Standardansatz unterstützen, klingt für den OO-Ent-wickler wie ein Taschenspielertrick. Schließlich haben allenicht trivialen Programme einen internen Zustand, den manirgendwo lesen und verändern können muss.

Der entscheidende Kniff bei Systemen, die (fast) aus-schließlich mit unveränderlichen Werten arbeiten, ist die kon-zeptionelle Unterscheidung zwischen der Identität eines Ob-jekts und dem aktuellen Zustand des Objekts. Während jede„normale“ Operation mit unveränderlichen Objekten arbei-tet, existiert für Entitäten – Objekte, deren Zustand sichüber die Zeit ändert – ein standardisierter Weg, um ihrenZustand zu aktualisieren. Was sich in der Theorie schwieriganhört, ist in der Praxis leichter verständlich; man passt dasBeispiel so an, dass es nur noch an einer Stelle mit verän-derlichem Zustand zu tun hat und ansonsten mit unverän-derlichen Wert-Objekten arbeitet. Eine unveränderlicheShelf-Klasse sieht in Groovy wie in Listingˇ4 aus. (Prinzi-

12 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG Multi-Core-Programmierung

Call for Papers für parallel 2012Am 23. und 24.ˇMai 2012 findet in der IHK in Karlsruhe die parallelˇ2012 statt, eine neue Softwarekonferenz für Parallel Pro-gramming, Concurrency und Multicore-Systeme. Die Veranstal-ter sind heise Developer, iX und der dpunkt.verlag. Die Konfe-renz will Softwarearchitekten, Entwicklern, Projektleitern undIT-Strategen Grundlagen sowie wesentliche Aspekte parallelerSoftwareentwicklung und nebenläufiger Programmierung ver-mitteln. Die Ausrichter fordern ausgewiesene Experten zur Pa-rallelprogrammierung auf, sich bis 31.ˇDezember 2011 mit Vor-trägen und Tutorials auf www.parallel2012.de zu bewerben.Gesucht werden Vorträge zu beispielsweise:

• etablierten Multithreading- und Synchronisationsmecha -nismen

• modernen Programmiermodellen und Parallelisierungsstrate-gien

• Entwurfsmustern für Parallelprogrammierung

• Erfahrungen mit verbreiteten Programmierplattformen wieJava, .Net und C/C++

• Erfahrungen mit Sprachen wie Ada, Erlang, F#, Fortran,Scala, Clojure und Groovy

• grundsätzlichen architektonischen Entscheidungen für denEinsatz parallelisierter Software

• Erfahrungsberichten von laufenden oder abgeschlossenenProjekten aus unterschiedlichen Industrien

• wichtigen Tools und Bibliotheken im Bereich der Parallel-programmierung

• parallelen Beschleunigern wie GPUs (CUDA, OpenCL undOpenGL)

Gewünscht sind entweder Langvorträge bis zu 90ˇMinuten oderkurze Sessions bis zu 40ˇMinuten. Tutorials sind als ganztägigeVeranstaltungen geplant.

class Shelf...void putIn(Product product) {

atomic {if (isFull())

throw new StorageException("shelf is full.")products << product

}}boolean takeOut(Product product) {

atomic {return products.remove(product)

}}

class Storehouse...boolean move(Product product, String from, String to) {

atomic {if (!shelves[to].isFull() && shelves[from].takeOut

(product)) {shelves[to].putIn(product)return true

}return false

}}

Listing 3: Shelf und Storehouse mit STM-Ansatz

Page 13: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

piell lässt sich der Ansatz auch in Java und C# umsetzen.Allerdings ist die korrekte Implementierung unveränder -licher Wertobjekte in klassischen OO-Sprachen oft müh-sam, siehe z.ˇB. [5].)

Die Annotation @Immutable sorgt während der Kompi-lierung dafür, dass alles im Bytecode hinzukommt, was dieJVM für ein ordentliches unveränderliches Objekt benötigt.Der große logische Unterschied zur veränderlichen Shelf-Va-riante ist, dass Methoden, die den Zustand verändern (take -Out und putIn), jetzt ein neues Shelf-Objekt zurückgeben,das bis auf die Produktliste selbst eine Kopie des ursprüng-lichen Objekts darstellt. „Kopie“ ist hierbei lediglich alsKonzept zu verstehen; aus Effizienzgründen setzt man häu-fig auf andere Implementierungen, wie der Autor weiter un-ten erläutert.

Um später feststellen zu können, aus welcher Version ei-nes Regals die veränderte Instanz hervorgegangen ist, spen-diert man der Klasse einen Versionszähler:

@Immutable class Shelf...long versionShelf incVersion() {

return new Shelf(capacity, products, version + 1)}

Die Shelf-Klasse ist somit eine reine Werte-Klasse; das Ver-walten der Shelf-Identitäten überlässt man dem Storehouse(siehe Listingˇ5). Übrig bleibt eine einzige synchronisierteMethode, nämlich die, die den Update von Shelf-Zuständenermöglicht. Als Parameter nimmt sie eine Map entgegen, je-weils mit dem Namen des Regals als Key und der neuen Re-galinstanz als Value. Die Methode überprüft zunächst, obsich eine der Versionen verändert hat, was auf eine neben-läufige Änderung schließen lässt. Wenn nicht, ersetzt sie denZustand aller übergebenen Shelf-Instanzen und erhöht derenVersionszähler. Das Vorgehen erlaubt die gleichzeitige undatomare Zustandsänderung mehrerer Werte, wie man sie

iX Developer 1/2012 – Programmieren heute

@Immutable class Shelf...int capacityList productsShelf putIn(Product product) {

if (isFull())throw new StorageException("shelf is full.")

return cloneWith(new ArrayList(products) << product)}Shelf takeOut(Product product) {

if (!products.contains(product))return this

return cloneWith(new ArrayList(products).minus(product))}private Shelf cloneWith(products) {

return new Shelf(capacity, products, version)}

Listing 4: Unveränderliches Shelf

class Storehouse {final shelves = [:] as ConcurrentHashMapfinal lock = new ReentrantLock()Shelf newShelf(String name, int size) {

shelves[name] = new Shelf(size, [], 0)}boolean update(Map names2shelves) {

lock {if (names2shelves.any {name, shelf ->

shelves[name].version != shelf.version}) return false

names2shelves.each {name, shelf ->shelves[name] = shelf.incVersion()

}return true

}}

Listing 5: Storehouse mit Update-Methode für Shelves

Page 14: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

beim Verschieben von Produkten benötigt (siehe Listing 6).Wieder erkennt der Leser das Muster optimistischer Opera-tionen: Sie werden so lange wiederholt, bis sie erfolgreichsind, was bei wenigen zu erwartenden Kollisionen fast nienotwendig sein wird, bei vielen verändernden Zugriffen aberdurchaus die Performanz negativ beeinflussen kann.

Der große Nachteil von Systemen, die auf unveränderli-che Werte setzen, ist neben dem für OO-Entwickler unge-wohnten Design die Performance – zumindest dann, wennman den Klonvorgang bei verändernden Funktionen naiv alsDeep Copy implementiert. In dem Fall benötigt der Ent-wickler nicht nur übermäßig viel Speicher, den anschließendder Garbage Collector wieder freigeben muss, sondern dasKopieren an sich braucht spürbar mehr Prozessorzyklen alseine „normale“ Zustandsänderung. Tatsächlich lässt sich Clo-ning oft ressourcenschonend programmieren, indem man le-diglich die Referenz auf den Vorgänger und zusätzlich dasZustandsdelta speichert. Sprachen, die auf Immutability alsHauptparadigma setzen, benutzen daher effizient implemen-tierte unveränderliche Datenstrukturen, zum Beispiel Clo -jures persistente Datenstrukturen.

Actors: Die wahren Objekte?Ein weiteres Paradigma der nebenläufigen Entwicklung er-lebt zurzeit eine Renaissance: Aktoren (Actors). Aktoren sindObjekte, die sich gegenseitig asynchrone und unveränder -liche Nachrichten hin- und herschicken und weitgehend aufeinen gemeinsam verwendeten Zustand verzichten. Ein Ak-tor kann einen anderen um Zustandsinformationen bitten undbekommt diese dann im Gegenzug zurückgeschickt. JederAktor hat seinen eigenen Briefkasten, aus dem er die Nach-richten nacheinander entnimmt und abarbeitet. Ein leichtge-wichtiges Threading- oder Prozess-Modell sorgt dafür, dassviele Aktoren parallel existieren und sich die verfügbarenRessourcen effizient teilen können. Manche behaupten so-gar, dass Aktoren genau das sind, was Alan Kay im Kopf hat-te, als er das Wort „objektorientiert“ erfand.

Bekannt wurden Aktoren durch die Sprache Erlangˇ[f];dort nennt man sie Agenten. Erlang nimmt für sich in An-spruch, mit dem Aktoren-Konzept hochverfügbare und feh-lertolerante Systeme bauen zu können. Insider berichten bei-spielsweise von 99,9999999ˇProzent erreichter Verfügbarkeitbei einem Telefon-Switch der Firma Ericsson. Bibliothekenfür die Aktoren-Programmierung gibt es sowohl für Java alsauch für .Net. Interessanter sind jedoch Programmierspra-chen, die das Konzept direkt unterstützen, neben Erlang etwaScala oder Groovy mit der GPars-Bibliothek.

Aktoren erleben zurzeit auch deshalb ihren zweiten Früh-ling, weil manche sie als das Silver-Bullet der nebenläufigenProgrammierung verkaufen: Sie fühlen sich beinahe an wiedie lieb gewonnenen Objekte und sind darüber hinaus unab-hängig, skalierbar und verteilbar. Mit ihnen lassen sich RaceConditions und Verklemmungen einfacher vermeiden. In derPraxis ist leider auch diese Wiese nicht so grün, wie sie vonWeitem aussieht: Die Skalierbarkeit im Großen hängt starkvon der darunterliegenden Plattform ab. Deadlocks und Co.sind zwar seltener, aber weiterhin möglich, und das Festlegenauf asynchrone Kommunikation gestaltet manche Imple-mentierung aufwendiger, als sie sein müsste. Der größte Fall-strick ist jedoch, dass Aktoren dann versagen, wenn man mitihnen Aufgaben angehen möchte, die einen Konsens über ge-meinsam verwendeten Zustand erfordern – beispielsweise ei-ne Transaktion über mehrere Nachrichten hinweg. Der Arti-kel auf Seiteˇ21 behandelt das Thema ausführlich.

Parallele Verarbeitung gleichförmiger DatenBislang hat sich der Autor auf das Vermeiden von Verklem-mungen und Race Conditions konzentriert. Die andere Seiteder Medaille ist keineswegs einfacher: Wie parallelisiert mandas Beispielprogramm so, dass Nebenläufigkeit überhauptmöglich ist, oder gar so, dass man mit einer nennenswertenPerformanceverbesserung durch den Einsatz mehrerer Kernerechnen kann?

Grundsätzlich steht eine Reihe von Möglichkeiten zurVerfügung: Hat der Entwickler es mit einem Standardpro-blem zu tun – wie der Berechnung der Zahl Pi (;-)) – fahndeter im Netz nach einem parallel ausführbaren Algorithmus. Inden anderen Fällen versucht er sich selbst an der Paralle -lisierung, meist nach einer der beiden Grundstrategien:• Parallelisierung des Kontrollflusses: Indem der Entwickler

die zeitlichen Vor- und Nachbedingungen aller Berech-nungsschritte betrachtet, ermittelt er die Teile des Pro-gramms, die nicht in einer zeitlichen Abhängigkeit zuein -ander stehen, und startet dann an den entsprechendenStellen einen neuen Task. Dabei können ihn Modelle(z.ˇB. Petri-Netze) unterstützen. Dadurch erreicht er zwarunter Umständen eine spürbare Beschleunigung des Pro-gramms, eine für große Mengen verfügbarer Rechenkerneskalierbare Lösung findet er so jedoch meist nicht.

• Datenparallelisierung: Häufiger ist die Situation, dass derProgrammierer es mit einer größeren Menge gleichartigerDaten zu tun hat, von denen jedes einzelne Datum auf diegleiche Weise durch das Programm zu schleusen ist. Indem Fall steht ihm plötzlich ein weiterer Weg offen: Erzerteilt die Daten in kleine Häppchen und wirft jedesHäppchen einem Rechenkern zum Fraß vor.

Der Gedanke paralleler Verarbeitung gleichförmiger Datenlässt sich mit der erwähnten Groovy-Bibliothek GPars leicht

14 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG Multi-Core-Programmierung

[a]ˇ Visual Studio Asynchronous msdn.microsoft.com/en-us/vstudio/Programming gg316360

[b]ˇGrand Central developer.apple.com/technologies/Dispatch mac/core.html#grand-central

[c]ˇ Clojure clojure.org[d]ˇGPars gpars.codehaus.org[e]ˇ Go golang.org[f]ˇ Erlang www.erlang.org

Onlinequellen

class Storehouse...boolean move(Product product, String from, String to) {

while(true) {def shelfTo = shelves[to]def shelfFrom = shelves[from]if (shelfTo.isFull()) return falsedef newShelfFrom = shelfFrom.takeOut(product)if (shelfFrom == newShelfFrom) return falseshelfTo = shelfTo.putIn(product)def shelvesToUpdate = [(from): newShelfFrom, (to):

shelfTo] if (update(shelvesToUpdate)) return true

}}

Listing 6: Optimistische Update-Operation

Page 15: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

umsetzen. Hier ein einfaches Beispiel, das die Quadratzah-len aller Elemente einer Zahlenliste bildet:

def numbers = [1, 2, 3, 4, 5, 6]def expected = [1, 4, 9, 16, 25, 36]groovyx.gpars.GParsPool.withPool {

def squares = numbers.collectParallel { it * it }assert squares == expectedassert squares.sumParallel() == 91

}

GPars stellt die entsprechenden *Parallel-Methoden zur Ver-fügung, die im Hintergrund einen Thread-Pool benutzen.Den einen oder anderen könnte überraschen, dass es auch ei-ne sumParallel-Methode gibt. Sie funktioniert so, dass je-weils zwei Summanden in einem eigenen Task addiert wer-den und das Ergebnis der Addition anschließend mit einemanderen Berechnungsergebnis wieder „gepaart“ wird, bisschließlich die Gesamtsumme ermittelt wurde.

Diese Art der Parallelisierung funktioniert nur effizient,wenn sich die jeweilige Berechnungen tatsächlich unab-hängig voneinander durchführen lassen und nicht etwa aufgemeinsame veränderliche Daten zugreifen. Das würdenämlich wieder explizite Synchronisation (z.ˇB. Locking) er-fordern, mit allen ihren negativen Auswirkungen auf Perfor-mance und Thread-Sicherheit. Eine spezielle Form der pa-rallelen Datenverarbeitung stellt der MapReduce-Ansatz dar.Diesen erläutert der Artikel auf Seiteˇ26.

Dataflow: Nebenläufigkeit wird wieder deterministischSobald nebenläufige Aufgaben komplexere Abhängigkeitenhaben, gerät man mit parallelen Mengenoperationen undMap Reduce-Ansätzen in Schwierigkeiten, da bei ihnen le-diglich am Ende der Berechnungskette eine Gesamtsynchro-nisation vorgesehen ist. Zu Hilfe kommt das Dataflow-Kon-zept; ein Satz aus [6] beschreibt die Idee gut: „Dataflowabstraction consists of concurrently run tasks communicatingthrough single-assignment variables with blocking reads.“Hier nur ein kleines GPars-Beispiel, um den Leser auf denGeschmack zu bringen:

import groovyx.gpars.dataflow.*import groovyx.gpars.dataflow.*import static groovyx.gpars.dataflow.Dataflow.taskfinal dichte = new DataflowVariable() final gewicht = new DataflowVariable() final volumen = new DataflowVariable() task { dichte < gewicht.val / volumen.val } task { gewicht < 10.6 } task { volumen < 5.0 } assert dichte.val == 2.12

Mit task {} kann man in GPars asynchron Aufgaben starten.dichte.val in der assert-Zeile wird daher so lange blockiert,bis die anderen Tasks ihre Wertzuweisungen mittels < erle-digt haben.

Dataflows haben die Eigenschaft, dass sie sich auch in ei-ner nebenläufigen Umgebung deterministisch verhalten. Dasbedeutet unter anderem, dass ein Deadlock, wenn er dennauftritt, immer auftritt, auch wenn man die einzelnen Aufga-ben sequenziell startet. Damit wird das Erstellen aussage-kräftiger Unit-Tests plötzlich wieder einfach. Darüber hinausreagieren Dataflow-Variablen in der Praxis wie unveränder-liche Werte und benötigen somit keinen Synchronisations -code – Race Conditions und Live Locks sind nicht möglich.

Den größten Nachteil stellt wohl auch hier die geänderteSicht auf den Lösungsraum dar. Entwickler sind nicht ge-wohnt, in Datenflüssen zu denken und müssen das (wieder)erlernen, wenn sie den Nutzen aus dieser mächtigen Abs-traktion ziehen wollen.

Der Artikel auf Seiteˇ31 geht detailliert auf die Eigen-schaften des Dataflow-Ansatzes ein.

FazitDer heilige Gral der Multi-Core-Programmierung ist noch lan-ge nicht gefunden. Die verbreitetste Idee besteht in der Lock-Synchronisation von Shared Mutable State. Dieser Ansatz istjedoch in der Praxis nur schwer zu zähmen, weil er der freienKomponierbarkeit von Objekten zuwiderläuft. In der Praxismüssen Entwickler daher je nach Aufgabenstellung und Kon-text aus der Menge der bekannten Programmierparadigmen ei-nen geeigneten Ansatz auswählen.

Benötigt man echtes nebenläufiges, transaktionales Ver-halten über beliebige Objekte hinweg, muss man den Preisdafür zahlen. Entweder in Form der fehlerträchtigen manuel-len Synchronisation, oder als noch (?) nicht ausgereifteSTM-Technik oder in der ungewohnten Variante versionier-ter unveränderlicher Zustandsobjekte. Eines jedoch ist klar:Je weniger Shared Mutable State ein System besitzt, destoeinfacher ist Thread-Sicherheit zu erreichen. Der Entwicklerersetzt daher zustandsbehaftete durch Wertobjekte, wo immerer kann. In vielen Fällen erledigen die eingesetzten Frame -works den Rest. Wer an der vorderen Front der Multi-Core-Programmierung mitmischen oder mitlauschen möchte,kommt um eine gehörige Portion Mehrsprachigkeit nichtherum. Das beweisen auch die übrigen Artikel dieses Son-derhefts zur Multi-Core-Programmierung. (ane)

JOHANNES LINKist freiberuflicher Coach für agile Softwareentwicklung.Mit dem Thema Multi-Core-Programmierung beschäftigter sich intensiv, seit er feststellen musste, dass sein neuerRechner weniger Gigahertz hatte als sein vorheriger.

Literatur[1]ˇBrian Goetz; Java Concurrency in Practice; Addison-

Wesley, 2006[2]ˇEdward A. Lee; The Problem with Threads;

www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf

[3]ˇBrian Goetz; Concurrency: Past and Present; www.infoq.com/presentations/goetz-concurrency-past-present

[4]ˇVenkat Subramaniam; Programming Concurrency on the JVM. Pragmatic Programmers, 2011

[5]ˇJoshua Bloch; Effective Java; Addison-Wesley, 2008[6]ˇVaclav Pech; Flowing with the Data; www.jroller.com/

vaclav/entry/flowing_with_the_data[7]ˇHerb Sutter; The Free Lunch is Over; www.gotw.ca/

publications/concurrency-ddj.htm[8]ˇJohannes Link; Simplified Use of Locks in Groovy;

blog.johanneslink.net/2011/10/25/simplified-use-of-locks-in-groovy

iX Developer 1/2012 – Programmieren heute 15

Alle Links: www.ix.de/ix1116006 x

Page 16: ellenmarkt - download.e-bookshelf.de€¦ · Compiler, Performance- und Parallel-Bibliotheken mit den Profiling-Werkzeugen Intel Inspector XE und Intel VTune Amplifyer XE (30-tägige

Clojure ist eine verhältnismäßig neue Programmiersprache,die einen starken Fokus auf die Entwicklung von „Multi-

threaded“-Programmen setzt. Als Mitglied der Familie derLisp-artigen Sprachen sowie durch die Verwendung der JavaVirtual Machine als Zielplattform vereint Clojure eine langeGeschichte und eine attraktive Bibliothekenlandschaft. Zu-dem ermutigt Clojure die Entwickler, einen funktionalen Pro-grammierstil zu verwenden, der heute für die Entwicklungvon Multithreaded-Programmen als geeignet gilt.

Die Erstellung robuster und skalierender Programme fürMultiprozessor- oder Mehrkernarchitekturen ist eines dergroßen Themen in der Softwareentwicklung. Die vorherr-schende Technik ist derzeit sicherlich das zunächst einfach er-scheinende Locking, das sich jedoch mit zunehmender Kom-plexität der Programme als immer schwieriger zu beherrschenerweist. Dead- und Live-Locks, Exceptions sowie verschach-telte Locks bereiten immer häufiger Kopfzerbrechen.

Software Transactional Memory ist ein Werkzeug für denZugriff auf geteilte Ressourcen. Dabei ist die Idee von„Transaktionen“ an sich nicht neu. Im Bereich der Daten-banken sind sie ein gängiges Verfahren, aber auch aus Spei-cherbereichen seit den 1980er-Jahren bekannt.

Unveränderliche DatenSTM ist sicherlich ein prominentes Feature der Program-miersprache Clojure und wird nur durch die Kombinationanderer, grundlegender Eigenschaften möglich. Die ersteGrundlage für die STM-Implementierung sind unveränder -liche Daten und Datenstrukturen. Dem liegt ein Konzept zu-

grunde, das Identitäten, Werte und Zustände trennt. Ein Wertist etwa eine Zahl oder ein String, eine Identität hingegen ei-ne mit einer Bedeutung behaftete Entität, die zu einem Zeit-punkt mit einem Wert verbunden ist. Die Verbindung einerIdentität mit einem Wert zu einer Zeit ist ein Zustand.

""Ich bin ein String-Literal: ein Wert"[:das :ist :ein :Vector :von :Keywords]2010 ; Eine Zahl. Ein Jahr? Ein Filmtitel?(def voriges-jahr 2010) (def identitaet "def verknuepft Wert und Identitaet"))

Die Werte in Clojure bestehen nicht nur aus primitiven Daten-typen wie Zahlen und Strings, sondern auch aus Datenstruktu-ren wie Vektoren und Maps. Diese sind unveränderlich – ein-mal angelegt, ändern sie sich nicht mehr. Dadurch lassen siesich ohne Komplikationen zwischen Threads austauschen.Eine einfache Implementierung unveränderlicher Daten-strukturen würde etwa beim Hinzufügen eines Elements zueinem Vektor eine komplette Kopie der bisherigen Werte an-legen. Offensichtlich hat das keine konstante Performance-Charakteristik, wie sie Vektoren in der Regel bieten.

Die Implementierung von Hash-Maps und Vektoren in Clo-jure umgeht das Problem, indem sie die Inhalte der Daten-strukturen in Bäumen ablegt. Diese haben maximal eine Tiefevon sechs Ebenen, die jeweils über 32ˇKnoten verfügen. DieAbbildungˇ1 zeigt einen (gegenüber der tatsächlichen Imple-mentierung etwas vereinfachten) Baum mit drei Ebenen. AlsIndex dienen die 32ˇBits eines Integer, die in Gruppen zu jefünf Bits für den Index in je einer Ebene verwendet werden.

Solcherart organisiert lassen sich abgeleitete Vektorenleicht anlegen, denn es ist höchstens der Pfad vom neuenoder veränderten Datenelement bis hinauf zu einem neuen

16 iX Developer 1/2012 – Programmieren heute

PARALLELPROGRAMMIERUNG STM

Software Transactional Memory am Beispiel Clojure

TransaktionsverarbeiterStefan Kamphausen

Software Transactional Memory (STM) ist ein Vorgehen für den Zugriff auf geteilte

Ressourcen. Dieser Artikel beleuchtet anhand der Program mier sprache

Clojure die Motivation dafür ebenso wie die Grund lagen und

Funktions weise, aber auch einige Fallstricke.