Erfolgreiche ePaper selbst erstellen
Machen Sie aus Ihren PDF Publikationen ein blätterbares Flipbook mit unserer einzigartigen Google optimierten e-Paper Software.
Programmieren<br />
www.linux-magazin.de TBB 3.0 05/2011<br />
100<br />
Abbildung 3: Beim Sudoku müssen alle Felder mit<br />
Zahlen von 1 bis 9 belegt werden. Eine Ziffer darf in<br />
Reihe, Spalte und Block nur einmal vorkommen.<br />
Zeiger übergibt. Da die TBB-Bibliothek<br />
nach Ende der »execute«-Methode die<br />
Task automatisch löscht, ist es allerdings<br />
unmöglich, Ergebnisse der Kind- an die<br />
Eltern-Task zu übergeben. Daher erhält<br />
die Kind-Task einen Zeiger auf eine Speicherstelle,<br />
in die das Ergebnis einzutragen<br />
ist. Da die Eltern-Task während der<br />
Ausführung der Kinder pausiert, ist sichergestellt,<br />
dass kein anderer auf diese<br />
Speicherstelle zugreift.<br />
Wie arbeitet nun die »execute()«-Methode?<br />
Der Aufruf »nextfree()« liefert das<br />
nächste freie Feld zurück. Anschließend<br />
testet eine Schleife für alle Zahlen zwischen<br />
1 und 9, ob diese als Kandidat<br />
für das Feld in Frage kommen. Für jeden<br />
ermittelten Kandidaten erzeugt das<br />
Programm eine neue Kind-Task und<br />
speichert diese in einer Liste. Der Aufruf<br />
»spawn_and_wait_for_all()« startet<br />
die Kinder-Tasks und pausiert die auf-<br />
rufende Eltern-Task. Wenn alle Kinder<br />
sich zurückgemeldet haben, werden die<br />
Ergebnisse eingesammelt.<br />
Listing 5 enthält das Hauptprogramm.<br />
Die Wurzel-Task erzeugt rekursiv sehr<br />
viele Kinder-Tasks. Den Rest erledigt die<br />
TBB-Bibliothek und verteilt diese Task-<br />
Objekte auf die einzelnen Threads. Der<br />
Entwickler ist vom Programmieren mit<br />
Threads und tückischen Problemen wie<br />
etwa Deadlocks befreit.<br />
Abbildung 4 zeigt die Skalierung des parallelen<br />
Lösers auf einer Hexacore-CPU für<br />
ein Sudoku. Je mehr Cores zum Einsatz<br />
kommen, umso schneller ist das Sudoku<br />
gelöst. Die lineare Kurve zeigt die maximal<br />
mögliche Beschleunigung. Bei sechs<br />
Kernen ist die tatsächlich messbare aber<br />
wegen des steigenden Verwaltungsaufwands<br />
nur knapp fünffach.<br />
Parallele Datenstrukturen<br />
Die TBB versprechen eine Reihe von Containerklassen,<br />
die im Gegensatz zur STL<br />
verbindlich Thread-safe sind. Allerdings<br />
ist das nicht ganz korrekt: Um beurteilen<br />
zu können, ob eine Klasse gegen einen<br />
gleichzeitigen Zugriff mehrerer Threads<br />
geschützt ist, muss man jede einzelne<br />
Methode der Klasse betrachten. Dabei<br />
wird deutlich, dass die Containerklassen<br />
der TBB nur Thread-sicher in Hinblick<br />
auf bestimmte Methoden sind. Beispielsweise<br />
sollte der Aufruf des Konstruktors<br />
oder Destruktors immer nur durch einen<br />
einzigen Thread erfolgen.<br />
Der »concurrent_vector« verhält sich<br />
Thread- safe, wenn ein neues Element mit<br />
Speed-Up<br />
Anzahl der Rechenkerne<br />
Real<br />
Linear<br />
Abbildung 4: Theoretisch steigt die Performance<br />
linear mit der Zahl der Kerne, doch in der Praxis<br />
fordert der Verwaltungsaufwand seinen Teil.<br />
»push_back()« angehängt wird. Paralleler<br />
Zugriff mit dem »[]«-Operator auf ein<br />
Element im Vektor ist möglich, aber nicht<br />
Thread- safe, das ist Sache des Entwicklers.<br />
Elemente lassen sich aus der Datenstruktur<br />
im Gegensatz zum STL-Pendant<br />
nicht einmal mehr entfernen.<br />
Einzig der Container »concurrent_hash_<br />
map« bietet die Möglichkeit, den gleichzeitigen<br />
Zugriff auf ein einzelnes Element<br />
zu verhindern. Hierzu gibt es Accessor-<br />
Objekte, also intelligente Zeiger auf ein<br />
Element in der Hashtabelle. Solange ein<br />
Zeiger existiert, dürfen andere Threads<br />
nicht auf das referenzierte Element zugreifen<br />
und werden suspendiert.<br />
Das Beispiel in Listing 6 zeigt die parallele<br />
Berechnung des Histogramms eines<br />
Grauwertbildes. Die Methode »insert()«<br />
fügt ein neues Element in die Hashtabelle<br />
ein, wenn dieses noch nicht existiert.<br />
Gleichzeitig wird ein Zugriffselement »a«<br />
Listing 5: Hauptprogramm<br />
01 #include <br />
02 #include <br />
03 using namespace std;<br />
04<br />
05 #include "tbb/task_scheduler_init.h"<br />
06 #include "tbb/tick_count.h"<br />
07 using namespace tbb;<br />
08<br />
09 #include "sudoku.h"<br />
10 #include "SudokuTask.h"<br />
11<br />
12 int main(int argc, char* argv[]) {<br />
13 cout print();<br />
20<br />
21 unsigned long subtasks=0;<br />
22 SudokuTask* root=new(task::allocate_root())<br />
SudokuTask(puzzle,&subtasks);<br />
23<br />
24 tick_count t0=tick_count::now();<br />
25 task::spawn_root_and_wait(*root);<br />
26 tick_count t1=tick_count::now();<br />
27<br />
28 cout