Open Source im professionellen Einsatz

Jens Körte, sxc.hu

Ein Template-basiertes Programmiermodell für Multicore

Baukasten

,

Mit den Threading Buildings Blocks 1.0 bietet Intel für Parallelprogrammierung ein C++-Template-basiertes kommerzielles Framework an. Zwei Intel-Entwickler führen in das komplexe Konzept ein, das sich für alle Multicore- und SMP-Systeme eignet, und stellen wichtige Features vor.

Für die Parallelisierung einer seriellen Anwendung gibt es verschiedene Vorgehensweisen. Interessant ist, dass sich in diesem Bereich nur Sprachergänzungen (OpenMP, Cilk) zu den gängigen Programmiersprachen (C/C++, Fortran) und Bibliotheksansätze (PVM, MPI, Pthreads, Win32-Threads) durchgesetzt haben und nicht etwa inhärent parallele Sprache wie Erlang. Auch die Intel Threading Building Blocks (TBB) bieten eine solche Bibliothek [1].

Thread-basierte Modelle

Die meisten parallelen Ansätze beruhen auf dem Prozesskontext-Modell. Der Kontext beinhaltet den vollständigen Zustand eines Prozesses zu einem bestimmten Zeitpunkt. Prozesse können ihrerseits mehrere nebenläufige Threads erzeugen, von denen jeder einzelne seriell abläuft. Die Reihenfolge und Priorität, mit denen die CPU die einzelnen Instruktionen oder Bereiche der Threads ausführt, ist dem Betriebssystem oder einem eigenen Steuerprogramm (Scheduler) überlassen.

Die Erzeugung von Threads ist deutlich schneller und effizienter als die Erzeugung von Prozessen, weil kein vollständiger Austausch des Prozesskontextes notwendig ist. Threads teilen sich eine Reihe von Betriebsmitteln mit dem zugehörigen Prozess, etwa Code- und Datensegmente sowie Dateideskriptoren. Jeder Thread besitzt aber einen eigenen Befehlszähler und Stack. Da Threads demselben Prozess zugeordnet sind, kommunizieren sie über den gemeinsamen Adressenraum. Diese Kommunikation ist sehr schnell, da der limitierende Faktor nur die Cache-Latenz oder die Speicherzugriffszeit ist.

Zu den Shared-Memory-Programmiermodellen gehören alle Ansätze, die im weitesten Sinne auf Multithreading basieren, also Thread-Bibliotheken wie Pthreads [2] oder Win32-Threads, Sprachen mit Thread-Erweiterungen wie Java und C# und Spracherweiterungen wie OpenMP (siehe den Artikel in diesem Schwerpunkt).

Dieser Artikel stellt die für die Threading Building Blocks relevanten Teile des Shared-Memory-Modells vor. Wie alle anderen Multithreading-Varianten greifen auch sie auf Betriebssystemaufrufe zum Erzeugen und Verwalten von Threads zurück. Bei gleichzeitiger Ausführung mehrerer Prozesse oder Threads muss das Betriebssystem oder ein eigener Scheduler die Zugriffe auf die vorhandenen Ressourcen wie CPU, Speicher und Festplatte regeln.

Der Scheduling-Algorithmus entscheidet zum Beispiel, wann und in welcher Reihenfolge die Instruktionen der verschiedenen Threads relativ zueinander ablaufen und wann ein Thread eine Speicherzelle ändert. Solche Zustandsänderungen sind besonders kritisch, wenn mehrere Threads auf denselben Speicher zugreifen. In solchen Fällen kann es zu so genannten Wettläufen (Race Conditions) kommen, die in der Praxis unvorhersehbar und oft nicht reproduzierbar sind, da sie von dem jeweiligen zeitlichen Ablauf abhängen.

Schwierige Fehlersuche

Ein weiteres Problem taucht immer dann auf, wenn sich Threads durch abhängige Zugriffe auf dieselben Ressourcen blockieren (Deadlock). Diese Mechanismen bewirken bei parallelen Programmen eine Unbestimmtheit, die sequenzielle Programme nicht aufweisen. Dadurch ergeben sich Probleme, die beim Entwicklungszyklus der Software zusätzliche Werkzeuge notwendig machen, um die Korrektheit des Ablaufs zu überwachen und Speicherzugriffsfehler von Threads aufdecken zu können.

Die Multithreading-Ansätze unterscheiden sich im Wesentlichen durch Granularität und Funktionsumfang. Dabei geht es im Kern darum, ob der Entwickler die maximale Kontrolle bei der Erzeugung und Synchronisation der Threads haben und damit auch den komplexesten und fehleranfälligsten Weg gehen möchte (Win32-Threads, Pthreads) oder ob er einen einfachen, eventuell inkrementellen Weg wie OpenMP wählt oder den Template-basierten Ansatz der Threading Building Blocks. Bei OpenMP wie auch bei TBB überlässt der Programmierer Details wie Erzeugung und Synchronisation der Laufzeitumgebung, gibt dadurch aber einen Teil der Kontrolle ab.

Ein richtig synchronisiertes paralleles Programm zu entwerfen ist deutlich komplexer und anfälliger für Fehler als der Entwurf seines sequenziellen Pendants. Das liegt an der Verwendung der Betriebssystemfunktionen zur Synchronisation von Threads sowie an den Verfahren zur Zugriffssteuerung, den so genannten Monitoren, Mutexen (Mutual Exclusion), Semaphoren und Locks.

Ein Mutex-Objekt zum Beispiel verhindert, dass nebenläufige Threads gleichzeitig auf Daten zugreifen und somit inkonsistente Zustände erzeugen können. Für einen objektorientierten Ansatz wäre es wünschenswert, wenn es gekapselte Varianten für den Zugriff auf die feingranularen Betriebssystemfunktionen sowie die Zugriffsbeschränkungen gäbe, die sich nahtlos in den objektorientierten Gesamtentwurf eingliedern.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook