Nebenläufigkeit heute: Eine Übersicht

Die Entwicklung von Software im Unternehmensumfeld war bis vor einigen Jahren gut beherrschbar. Im Zweifel wurde  für eine neue Aufgabe ein größerer Server angeschafft. Die heutigen Aufgaben müssen aber mit solchen Datenvolumen umgehen, dass dies keine Lösung mehr ist. Außerdem hat sich die Infrastruktur weiterentwickelt. Es gibt nicht größere Server, sondern mehr Server. Was bedeutet das für die traditionelle Software-Entwicklung? Ein Blick in die Infrastruktur-Ecke hilft.

Die moderne Infrastruktur

Um mit größeren Datenmengen oder mehr Anfragen umzugehen, gibt es nur eine beschränkte Menge an Alternativen:

  • Den sequenziellen Ablauf beschleunigen. Dies erfolgt unter anderem über das Chip-Design und die Taktfrequenz.
  • Pseudo-Parallelität ermöglichen. Hierbei führt die CPU einen weiteren Prozess (“Thread”) aus, während sie auf Rückmeldung wartet.
  • Mehrere Threads ausführen. Dies kann entweder auf Ebene der CPU erfolgen (“Multi-Core”), als auch auf Ebene der einzelnen Verarbeitungs-Linie (“Pipeline”, “Multi-Threading”)

Ein Blick in die aktuellen CPU-Architekturen zeigt, dass alle Varianten genutzt werden. Als Beispiel sollen ein Prozessor und ein Komplett-System dienen.

Der Prozessor: UltraSPARC T2

Eine ausführliche Beschreibung findet sich bei Wikipedia. Folgende Fakten sind aber relevant:

The processor, […], is available with eight CPU cores, and each core is able to handle eight threads concurrently. Thus the processor is capable of processing up to 64 concurrent threads. […] Speed bump for each thread, increased to 1.6 GHz from 1.2 GHz.

Was bedeutet dies nun für eine Anwendung, die auf solch einem System ausgeführt wird? Eine Anwendung, die keinen Nutzen aus paralleler Verarbeitung ziehen kann, wird mit 1.6 GHz ausgeführt. Außerdem wird die CPU bei solch einer Anwendung schlicht überhaupt nicht ausgenutzt. Um einen solchen Prozessor effizient zu nutzen, bedarf es einer massiven Ausnutzung von Parallelität.

Das optimale Nutzungsszenario für eine solche CPU ist die Verwendung von 64 Threads, die alle permanent ausgenutzt werden. Der Grund hierfür liegt darin, dass jeder Thread-Wechsel auf einer Pipeline eine Menge Zeit kostet. Die Nutzung von mehr als 64 Threads bringen also theoretisch nur weitere Nachteile mit sich.

Im Vergleich zu anderen Prozessoren jenseits der 3 Ghz sei noch angemerkt, dass dieser Prozessor im Single-Thread-Betrieb nicht sonderlich schnell ist. Um eine hohe Verarbeitungsgeschwindigkeit zu erreichen, kann auch hier nur Parallelität genutzt werden.

Das Komplett-System: SeaMicro SM10000-64HD

Auf der Homepage des Herstellers kann man detaillierte Daten zu dem System nachlesen, allerdings ist der folgende Auszug schon interessant genug:

The SeaMicro SM10000 family of servers is the first purpose built for scale-out workloads. Designed to replace 60 1 RU Dual Socket Quad Core servers, the SM10000-64HD integrates 768 Intel Atom low power x86 cores […]

Ein einzelnes System mit 768 Intel Atom Prozessorkernen! Solch ein System eignet sich sehr gut dafür, massiv parallele Berechnungen durchzuführen oder sehr viele, virtualisierte Umgebungen zu betreiben. Damit sich das ganze effizient nutzen läßt, bedarf es aber Softwaremöglichkeiten, Probleme über die Grenzen eines einzelnen Prozessors hinweg zu skalieren.

Und das bedeutet …

Die obigen exemplarischen Beispiele sollen eines verdeutlichen: bei der Nutzung einer modernen Infrastruktur geht es nicht mehr ohne Parallelität/Multithreading sowie Verteilung über Rechnergrenzen hinweg. Die Frage ist nun, wie kann dies geschehen?

Anforderungen an die Software-Entwicklung

Die Entwicklung von nebenläufigen Anwendungen (Multi-threading) und verteilten Systemen ist so alt wie das Internet. Für moderne Architekturen, wie oben, gibt es unterschiedliche Ansätze. Klassische Lösungen sind dabei:

  • Multi-Threading: Eine Anwendung wird dabei in mehreren Threads ausgeführt. Dies funktioniert gut, solange sich das Problem auf eine CPU beschränkt.
  • Message-oriented Middleware (MoM): Hierbei wird von Sendern Nachrichten an eine zentrale Warteschlange gesendet und von Empfängern gelesen. Durch die Möglichkeit, Sender und Empfänger zu multiplizieren, können auch über Rechnergrenzen hinweg Berechnungen durchgeführt werden.

Die Probleme mit diesen Ansätzen sind genauso vielschichtig, wie die Erfolge. Das Problem des Multi-Threadings ist, dass es hohe Anforderungen an den Entwickler stellt, um fehlerfrei solche Anwendungen zu entwickeln. Probleme wie Synchronisation und Deadlocks sind dabei allgegenwärtig.

Die Probleme der MoM liegen in dem zusätzlichen Overhead, der gesteigerten Komplexität, sowie dem potenziellen SPOF (Single Point of Failure) durch eine weitere technische Komponente. Man kann all diese Probleme zwar in den Griff bekommen, aber nur auf Kosten von weiterer Komplexität.

Demgegenüber stehen moderne Ansätze:

  • Shared-nothing Ansatz:Ein Programmierparadigma, dass Skalierbarkeit dadurch ermöglicht, dass es keine expliziten Abhängigkeiten von zwei Komponenten zueinander gibt.
  • Aktoren-Modell:Hierbei wird das Modell der MoM in einer Programmiersprache möglich gemacht und dabei die Komplexität verringert. Eine effiziente Implementierung minimiert optimiert dabei die Anzahl der genutzten Threads, um möglichst wenige Threads neu zu erzeugen.
  • Software Transactional Memory: Hierbei wird Nebenläufigkeit durch explizite Transaktionen (wie man sie von Datenbanken kennt) vereinfacht.

Fazit

Der Trend zu mehr Prozessoren und mehr Nebenläufigkeit ist ganz klar da und wird sich fortsetzen. Die streng sequenzielle Bearbeitung von Berechnungen und Problemen gehört der Vergangenheit an und wird in immer weniger von Bedeutung sein. Um diese neuen Herausforderungen zu meistern, bedarf es neuer Ansätze, um die Software-Entwicklung nicht noch komplexer zu machen, als sie bereits ist. Mit dem shared-nothing Ansatz, dem Aktoren-Modell und dem software-transactional Memory stehen verschiedene Modelle zu Verfügung, verteilte Probleme elegant zu lösen.

 

Schreibe einen Kommentar