| 1 |
root |
1.1 |
=encoding utf-8 |
| 2 |
|
|
|
| 3 |
|
|
=head1 Threads in dynamischen Sprachen und warum Perl-Pseudo-Threads sterben sollten |
| 4 |
|
|
|
| 5 |
|
|
Wie man am Titel sieht, soll es im folgenden um zwei verwandte Themen |
| 6 |
|
|
gehen. Einerseits möchte ich grundsätzliche Gedanken über Threads |
| 7 |
|
|
liefern, einige Vorurteile ausräumen und erläutern, wie Threads in |
| 8 |
|
|
dynamischen Sprachen implementiert werden und warum dies so ist. |
| 9 |
|
|
|
| 10 |
|
|
Andererseits möchte ich erläutern, warum Perl keine Threads unterstützt |
| 11 |
|
|
und weshalb Perls sogenannte Threads eher sterben sollten, da sie |
| 12 |
|
|
mehr Schaden anrichten als sie helfen. Letzteres muss nicht einmal zu |
| 13 |
|
|
Inkompatibilitäten führen: da Perl-Threads keine sind, lassen sie sich |
| 14 |
|
|
effizient ohne emulieren. |
| 15 |
|
|
|
| 16 |
|
|
=head2 Was ist ein Thread - Eine Begriffsbestimmung |
| 17 |
|
|
|
| 18 |
|
|
Am einfachsten erklärt man Threads, in dem man sie Prozessen |
| 19 |
|
|
gegenüberstellt: |
| 20 |
|
|
|
| 21 |
|
|
Prozesse haben (üblicherweise) einen eigenen Adressraum, d.h. Variablen |
| 22 |
|
|
existieren als Kopie in jedem Prozess und sind unabhängig |
| 23 |
|
|
voneinander. Außerdem besitzt ein Prozess eine "Continuation", d.h. im |
| 24 |
|
|
wesentlichen den aktuellen "Ort" der Programmausführung. Unter Unix |
| 25 |
|
|
kommen dann noch andere Ressourcen wie File-Descriptoren hinzu, aber im |
| 26 |
|
|
wesentlichen ist es das. |
| 27 |
|
|
|
| 28 |
|
|
Ein Thread nun ist im wesentlichen ein abgespeckter Prozess (daher werden |
| 29 |
|
|
sie auch oft "LWPs" - Light Weight Processes genannt), bei dem eine oder |
| 30 |
|
|
mehrere dieser Ressourcen gemeinsam genutzt werden, insbesondere der |
| 31 |
|
|
Adressraum. Daher kann man Threads auch als Untereinheit von Prozessen |
| 32 |
|
|
ansehen: ein Prozess kann aus ein oder mehreren Threads bestehen, die sich |
| 33 |
|
|
alle den gleichen Adressraum (die gleichen Variableninhalte) teilen. |
| 34 |
|
|
|
| 35 |
|
|
Bei Threads gibt eine Menge verschiedene Variationen. Die wichtigsten sind: |
| 36 |
|
|
|
| 37 |
|
|
=over 4 |
| 38 |
|
|
|
| 39 |
|
|
=item Kooperative Threads |
| 40 |
|
|
|
| 41 |
|
|
Die "Urform" der Threads - diese Threads werden "kooperativ" genannt, |
| 42 |
|
|
nicht, weil sie es sind, sondern, weil sie es sein müssen: Sobald |
| 43 |
|
|
ein Thread ausgeführt wird, läuft dieser ohne Unterbrechung, bis er |
| 44 |
|
|
freiwillig die CPU an einen anderen Thread abgibt. Tut ein Thread dies |
| 45 |
|
|
nicht, läuft nichts anderes im System. |
| 46 |
|
|
|
| 47 |
|
|
Manchmal werden Kooperative Threads auch Koroutinen, Fibers oder |
| 48 |
|
|
Continuations genannt, was genau genommen falsch ist, aber im wesentlichen |
| 49 |
|
|
bezeichnen diese Begriffe dasselbe. |
| 50 |
|
|
|
| 51 |
root |
1.2 |
Die Hauptvorteile von kooperativen Threads ist die vergleichsweise |
| 52 |
|
|
einfache Programmierung (Race-conditions sind wesentlich einfacher zu |
| 53 |
|
|
vermeiden) und ihre hohe Effizienz. Hauptnachteil ist, daß ein einzelner |
| 54 |
|
|
wildgewordener Thread das gesamte System lahmlegen kann. |
| 55 |
|
|
|
| 56 |
root |
1.1 |
Windows 3.11, das Oberon-System oder das Coro-Modul von Perl sind |
| 57 |
|
|
Beispiele für kooperatives Threading. |
| 58 |
|
|
|
| 59 |
|
|
=item Preemptive Threads |
| 60 |
|
|
|
| 61 |
|
|
Um das Problem der Monopolisierung des Rechners durch einen unkooperativen |
| 62 |
|
|
(weil z.B. ferhlerhaften) Thread zu umgehen, wurden "preemptive" Threads |
| 63 |
|
|
erfunden: Diese werden ohne ihr Zutun regelmäßig "unterbrochen" (z.B. |
| 64 |
|
|
durch einen Timer) und dadurch von der CPU "verdrängt" (preempted). |
| 65 |
|
|
|
| 66 |
|
|
Diese Art von Threads wird oft auch "time-slicing" (Zeitscheibenverfahren) |
| 67 |
|
|
genannt, da hierbei ein Thread üblicherweise eine bestimmte Zahl von |
| 68 |
|
|
Zeiteinheiten erhält und, wenn er in dieser Zeit die CPU nicht freigibt, |
| 69 |
|
|
wird er automatisch verdrängt. |
| 70 |
|
|
|
| 71 |
root |
1.2 |
Hauptvorteil von preemptiven Systemen ist, daß man wildgewordene |
| 72 |
|
|
Programme abbrechen kann, d.h. sie legen nicht das gesamte System |
| 73 |
|
|
lahm. Hauptnachteil ist, daß ihre Benutzung sehr kompliziert ist: auch |
| 74 |
|
|
Experten machen regelmäßig Fehler und erzeugen Race-Conditions. |
| 75 |
|
|
|
| 76 |
root |
1.1 |
Neuere Windows-Versionen, alle Unix-Versionen und insbesondere alle |
| 77 |
|
|
Thread-Systeme dynamischer Sprachen (d.h. Ruby, Python usf.) benutzen |
| 78 |
|
|
dieses Verfahren. |
| 79 |
|
|
|
| 80 |
|
|
=item "Kernel-Threads" und "Userspace-Threads" |
| 81 |
|
|
|
| 82 |
|
|
Ein weiterer wichtiger Unterschied zwischen Thread-Systemen ist, ob sie |
| 83 |
|
|
vom Betriebssystem selbst implementiert werden oder innerhalb eines |
| 84 |
|
|
Prozesses. |
| 85 |
|
|
|
| 86 |
|
|
In letzterem Fall teilen sich alle Threads dieselbe Prozess-Continuation, |
| 87 |
|
|
was nichts anderes bedeutet, als daß diese Threads niemals parallel |
| 88 |
|
|
laufen können (z.B. auf einem SMP-System). |
| 89 |
|
|
|
| 90 |
|
|
Der Begriff "Kernel-Thread" bezeichnet im Gegensatz dazu die Variante, bei |
| 91 |
|
|
er die Threads tatsächlich parallel/gleichzeitig arbeiten können, obwohl |
| 92 |
|
|
dies nicht zwangsweise im "Kernel" implementiert werden muss. |
| 93 |
|
|
|
| 94 |
|
|
Userspace-Threads sind im allgemeinen um ein vielfaches schneller als |
| 95 |
|
|
Kernel-Threads - die Userspace-Threading-Bibliothek, die von Coro benutzt |
| 96 |
|
|
wird schaltet mehr als 100 mal schneller zwischen Threads um als der |
| 97 |
|
|
Linux-Kernel. Selbst auf Perl-Ebene schalten die Userspace-Threads von |
| 98 |
|
|
Coro mehr als 14 mal schneller als die Linux-Kernel-Threads. |
| 99 |
|
|
|
| 100 |
root |
1.2 |
Kernel-Threads verschärfen gegenüber Userspace-Threads die Problematik |
| 101 |
|
|
der Programmierung nochmals. Ein gutes Beispiel ist MythTV, ein |
| 102 |
|
|
C++-Programm, daß Threads benutzt: auf Single-Core-Systemen arbeitet |
| 103 |
|
|
es meist korrekt, auf Multi-Core-Systemen sind crashes aber an der |
| 104 |
|
|
Tagesordnung, da der Programmierer offenbar nicht damit rechnet, |
| 105 |
|
|
daß Speicherzugriffe nicht mehr atomar sind bzw. die Gefahr einer |
| 106 |
|
|
Race-Condition stark erhöht ist. |
| 107 |
|
|
|
| 108 |
root |
1.1 |
=back |
| 109 |
|
|
|
| 110 |
|
|
=head2 Wozu wurden Threads erfunden, wozu eignen sie sich? |
| 111 |
|
|
|
| 112 |
|
|
=head3 Single-Cores |
| 113 |
|
|
|
| 114 |
|
|
Multi-Core und Multiprozessorsysteme sind erst seit kurzer Zeit |
| 115 |
|
|
verbreitet. Die überwiegende Mehrheit der Rechner besitzt nach wie vor |
| 116 |
|
|
nur einzelne CPUs. |
| 117 |
|
|
|
| 118 |
|
|
Moderne CPUs besitzen im allgemeinen spezielle Hardware, um Prozesse zu |
| 119 |
|
|
unterstützen, z.B. eine Memory-Management-Unit (MMU) um den Adressraum |
| 120 |
|
|
zu verwalten und einen Translation-Lookaside-Buffer (TLB) um das ganze zu |
| 121 |
|
|
beschleunigen. |
| 122 |
|
|
|
| 123 |
|
|
Da jeder Prozess seinen eigenen Adressraum besitzt, muss die MMU bei jedem |
| 124 |
|
|
Umschalten neu konfiguriert werden, Caches wie der TLB gehen häufig |
| 125 |
|
|
verloren. Dies kann die Performance empfindlich beeinflussen, da die meisten |
| 126 |
|
|
CPUs nur einen Zustand im Cache behalten können. |
| 127 |
|
|
|
| 128 |
|
|
Die natürliche Lösung gegen zu viele Rekonfigurationen sind weniger |
| 129 |
|
|
Rekonfigurationen bei Prozesswechseln. Wechselt man den Adressraum nicht, |
| 130 |
|
|
so erhält man Threads auf natürliche Weise, da die CPU einfach den alten |
| 131 |
|
|
Adressraum weiter benutzt. |
| 132 |
|
|
|
| 133 |
|
|
Daher stammt der Name "Light Weight Processes" (leichtgewichtige |
| 134 |
|
|
Prozesse): Threads werden hier als Prozesse mit weniger "Zustand" behandelt. |
| 135 |
|
|
|
| 136 |
|
|
Dies beschleunigt das Prozessumschalten erheblich, vor allem auf älteren |
| 137 |
|
|
Systemen. |
| 138 |
|
|
|
| 139 |
|
|
=head3 Multiprozessor- oder Multi-Core-Systeme |
| 140 |
|
|
|
| 141 |
|
|
Ein weit verbreiteter Irrtum ist es, daß Threads sich gut für die |
| 142 |
|
|
Parallelisierung eignen, d.h. echte Parallelverarbeitung auf einem |
| 143 |
|
|
Multiprozessorsystem. |
| 144 |
|
|
|
| 145 |
|
|
Dies ist allerdings nicht der Fall: Das Problem, daß es nur eine Instanz |
| 146 |
|
|
der Caches oder MMU gibt, existiert bei mehreren CPU-Kernen nicht, da |
| 147 |
|
|
diese alle ihren eigenen Cache besitzen. |
| 148 |
|
|
|
| 149 |
|
|
Im Gegenteil: Dadurch, daß Threads viele Ressourcen gemeinsam nutzen, |
| 150 |
|
|
muss das Betriebssystem durch aufwändige (und langsame) Verwaltung |
| 151 |
|
|
sicherstellen, daß auch alle CPUs im System den gleichen Zustand haben. |
| 152 |
|
|
|
| 153 |
|
|
Wenn z.B. ein Prozess mit mehreren Threads auf einem Multi-Core-System |
| 154 |
root |
1.2 |
arbeitet und der eine Thread vom Betriebssystem Speicher anfordert, |
| 155 |
|
|
so muss das Betriebssystem synchron alle anderen CPUs anhalten, ihnen |
| 156 |
|
|
den neuen Zustand mitteilen, warten, bis alle den Zustand übernommen |
| 157 |
|
|
haben und kann erst dann weiterarbeiten. Auf einem Dual-Core-System mag |
| 158 |
|
|
dies noch akzeptabel sein, bei mehr Cores wird dies aber schnell sehr |
| 159 |
|
|
aufwändig, auf Mehrfachprozessorsystemen, bei denen die Kommunikation |
| 160 |
|
|
zwischen CPUs noch länger braucht, wird dies schnell inakzeptabel. |
| 161 |
|
|
|
| 162 |
|
|
Aber auch der ganze normale Speicher-Cache wird nicht effizient |
| 163 |
|
|
genutzt: Da Threads alle Variablen gemeinsam nutzen, liegen gemeinsam |
| 164 |
|
|
genutzte Variablen nicht im lokalen Cache einer CPU, im Gegenteil, sie |
| 165 |
|
|
wandern häufig zwischen den Caches hin- und her, d.h. die Kommunikation |
| 166 |
|
|
läuft über den (vergleichsweise langsamen) Hauptspeicher ab. |
| 167 |
|
|
|
| 168 |
|
|
Letzteres wird häufig dadurch verhindert, daß man unterschiedliche |
| 169 |
root |
1.5 |
Speicherbereiche in jedem Thread verwendet. Allerdings wird dies durch |
| 170 |
root |
1.2 |
eine zusätzliche Indirektion bei jedem Zugriff bezahlt. Perls, die diese |
| 171 |
|
|
Methode benutzten, werden dadurch zwischen 15 und ca. 200% langsamer als |
| 172 |
|
|
Perls, die dies nicht tun (auch ohne Parallelbetrieb). |
| 173 |
|
|
|
| 174 |
|
|
Ein Vorteil von Threads ist die vergleichsweise schnelle Kommunikation |
| 175 |
|
|
über den Speicher, was bei Programmen, die viel Kommunikation betreiben, |
| 176 |
|
|
einen großen Gewinn bedeutet, wenn die alternative z.B. Kommunikation über Sockets ist. |
| 177 |
|
|
|
| 178 |
|
|
Moderne Betriebssysteme erlauben es aber ausnahmslos, einzelne |
| 179 |
|
|
Speicherbereiche gemeinsam zu nutzen, so daß Prozesse mit selektiv |
| 180 |
|
|
gemeinsam genutzten Speicher wesentlich effizienter auf Mehr-CPU-Systemen |
| 181 |
|
|
arbeiten, als Threads. |
| 182 |
|
|
|
| 183 |
root |
1.3 |
=head3 Ein Benchmark: Matrixmultiplikation |
| 184 |
|
|
|
| 185 |
root |
1.2 |
Am folgenden "Benchmark" wird dies deutlich. Das Benchmark-Programm |
| 186 |
|
|
implementiert eine parallel Matrixmultiplikation, bei der einzelne |
| 187 |
|
|
Zeilen und Spalten an vier "Arbeits-Threads" gesendet werden, die eine |
| 188 |
|
|
Skalarmultiplikation durchführen und an einen Ergebnis-Thread senden. |
| 189 |
|
|
|
| 190 |
|
|
Dabei kann es Perl-Pseudo-Threads (die parallel arbeiten) und Coro-Threads |
| 191 |
|
|
benutzen. |
| 192 |
root |
1.1 |
|
| 193 |
root |
1.2 |
Das Programm findet sich unter F<http://data.plan9.de/matmult>, ist aber |
| 194 |
|
|
für die Diskussion nicht relevant. |
| 195 |
root |
1.1 |
|
| 196 |
|
|
=begin latex |
| 197 |
|
|
|
| 198 |
|
|
\begin{center} |
| 199 |
|
|
\includegraphics{mat2.eps} |
| 200 |
|
|
\end{center} |
| 201 |
|
|
|
| 202 |
|
|
=end latex |
| 203 |
|
|
|
| 204 |
root |
1.3 |
=head4 Beschreibung |
| 205 |
|
|
|
| 206 |
root |
1.2 |
Das doppelt-logarithmische Diagramm stellt die Zahl der Anzahl der |
| 207 |
|
|
Matrixmultiplikationen pro Sekunde der Matrixgröße gegenüber. Die |
| 208 |
|
|
Größe der Matrix hat qualitativ wenig Einfluss, was zählt, ist der |
| 209 |
|
|
relative Unterschied zwischen den Verfahren. Das Testsystem benutzt eine |
| 210 |
|
|
relativ moderne Quad-Core-CPU. |
| 211 |
|
|
|
| 212 |
|
|
Im Diagramm finden sich vier Kurven: die unterste (und langsamste) |
| 213 |
|
|
entspricht den Perl-Pseudo-Threads, die auf allen Cores laufen dürfen und |
| 214 |
|
|
dies auch weitestgehend tun (die CPU-Auslastung betrug ca. 80%). Darüber |
| 215 |
|
|
befindet sich das gleich Programm, allerdings diesmal auf einen einzelnen |
| 216 |
|
|
Core beschränkt (was im Prinzip Unsinn ist, da immer noch die gleiche |
| 217 |
|
|
Anzahl Threads arbeitet). |
| 218 |
|
|
|
| 219 |
|
|
Darüber befinden sich zwei Kurven, die Coro-Threads benutzen. Die obere, |
| 220 |
|
|
langsamere wurde auf einen Perl mit Pseudo-Thread-Support erzeugt, |
| 221 |
|
|
die darunterliegende auf einem Perl ohne, das Programm selbst war |
| 222 |
|
|
identisch. Da Coro-Threads nicht parallel arbeiten, liefen beide auf einem |
| 223 |
|
|
einzigen Core. |
| 224 |
|
|
|
| 225 |
|
|
Was sofort auffällt, ist der starke Unterschied zwischen echter |
| 226 |
root |
1.3 |
Parallelverarbeitung und kooperativen Coro-Threads: Die Version mit echten |
| 227 |
|
|
Threads ist am rechten Rand mehr als I<326> mal I<langsamer> als die |
| 228 |
|
|
Version die nur eine CPU benutzt. |
| 229 |
|
|
|
| 230 |
|
|
Weniger auffällig, aber umso interessanter ist es, daß das |
| 231 |
|
|
Perl-Thread-Programm auf einer einzelnen CPU fast 20 mal schneller |
| 232 |
|
|
arbeitet als auf 4 Cores, obwohl beide Pseudothread-Varianten identisch |
| 233 |
|
|
sind. |
| 234 |
|
|
|
| 235 |
|
|
Der Unterschied der Coro-Varianten ist in diesem Diagramm nicht sehr |
| 236 |
|
|
deutlich, beträgt immerhin noch zwischen 25% und 35%, im Vergleich zu den |
| 237 |
root |
1.4 |
Pseudothread-Varianten verschwindend gering (dies ist ein AMD64-Perl, ein |
| 238 |
|
|
x86-Perl hat weitaus höhere Overheads). |
| 239 |
root |
1.3 |
|
| 240 |
|
|
=head4 Diskussion |
| 241 |
|
|
|
| 242 |
|
|
Der Benchmark wurde absichtlich so gewählt, daß die Kommunikation einen |
| 243 |
|
|
entscheidenden Bestandteil bildet. |
| 244 |
|
|
|
| 245 |
|
|
Die Pseudo-Threads von Perl sind hierbei doppelt im Nachteil: einerseits |
| 246 |
|
|
besitzen sie keinen gemeinsamen Adressraum (d.h. Variablen müssen kopiert |
| 247 |
|
|
werden), anderseits ist es durch Threads implementiert, und hat dadurch |
| 248 |
|
|
einen erhöhten Aufwand bei der Verwaltung (doppelte Indirektion). |
| 249 |
|
|
|
| 250 |
|
|
Dies allein kann den gewaltigen Unterschied von 31595% (!) erklären. |
| 251 |
|
|
|
| 252 |
|
|
Der Unterschied zwischen den beiden Pseudothreaded-Kurven ist schwerer zu |
| 253 |
|
|
erklären: Da die Programme identisch sind, muss der Unterschied durch |
| 254 |
|
|
andere Effekte erklärt werden: Die Kommunikation läuft in einem Fall |
| 255 |
|
|
weitestgehend über den CPU-Cache, während dieser bei mehrere Cores |
| 256 |
|
|
nicht benutzt werden kann, hier also die Kommunikation über den L2-Cache |
| 257 |
|
|
(ca. 10 mal langsamer) oder den Hauptspeicher (ca. 50 mal langsamer) |
| 258 |
|
|
abläuft. |
| 259 |
|
|
|
| 260 |
|
|
Hinzu kommt, daß während der Kommunikation gemeinsam genutzte Variablen |
| 261 |
|
|
gegen Zugriffe anderer CPUs geschützt werden müssen. Versuchen |
| 262 |
|
|
mehrere Threads gleichzeitig auf diese zuzugreifen, müssen sie sich |
| 263 |
|
|
synchronisieren. Auch dies läuft über den Hauptspeicher ab und ist noch |
| 264 |
|
|
dazu wesentlich aufwändiger. |
| 265 |
|
|
|
| 266 |
|
|
Der Unterschied von ca. 30% bei den beiden Coro-Varianten erklärt |
| 267 |
|
|
sich alleine dadurch, daß ein Pseudo-Threaded-Perl wesentlich mehr |
| 268 |
|
|
Aufwand treiben muss, um die Adressräume der Threads künstlich |
| 269 |
|
|
auseinanderzuhalten, mithin dupliziert es die Arbeit der MMU die in der |
| 270 |
|
|
CPU eingebaut ist und, obwohl Perl effizienter ist, ist eine Software-MMU |
| 271 |
|
|
natürlich immer noch langsamer als eine gut optimierte Hardwarelösung. |
| 272 |
|
|
|
| 273 |
|
|
|
| 274 |
|
|
=head2 Die Herausforderung: Threads in dynamischen Sprachen |
| 275 |
|
|
|
| 276 |
|
|
Thread-Programmierung ist komplex, vor allem, wenn die verwendeten Threads |
| 277 |
|
|
preemptiv sind oder sogar echt parallel arbeiten. Ein gutes Beispiel ist |
| 278 |
|
|
das "double-checked-locking"-Pattern, daß zehn Jahre in jedem Fachbuch |
| 279 |
|
|
stand, bis man feststellte, daß es nicht stabil arbeitet. |
| 280 |
|
|
|
| 281 |
|
|
Für "hardwarenahe" Sprachen ist es sehr viel einfacher, mit Threads |
| 282 |
|
|
umzugehen: viele Datentypen (z.B. C<int> in C) erlauben atomare Zugriffe, |
| 283 |
|
|
Locking ist explizit, der Zugriff selbst (nur lesend oder verändernd) ist |
| 284 |
|
|
genau festgelegt und so weiter. Und wenn's Crasht dann Crasht's eben - in |
| 285 |
|
|
C ist das nichts ungewöhnliches. |
| 286 |
|
|
|
| 287 |
|
|
Für Sprachen wie Perl oder Python stellt sich das ganze anders |
| 288 |
|
|
dar: Selbst "einfache" Integer-Variablen sind auf C-Ebene durch viele |
| 289 |
|
|
Variablen repräsentiert. Dadurch gibt es keine atomaren Zugriffe, d.h. |
| 290 |
|
|
auch lesend zugreifen kann man nur indem man ein Lock benutzt. Hinzu |
| 291 |
|
|
kommt, daß viele Zugriffe, die man als Programmierer als "Lesezugriff" |
| 292 |
|
|
ansieht, intern tatsächlich Schreibzugriffe auslöst. Ein Beispiel: |
| 293 |
|
|
|
| 294 |
|
|
$a .= $b; |
| 295 |
|
|
|
| 296 |
|
|
Ganz klar, C<$a> wird verändert. Nicht so offensichtlich ist, daß auch |
| 297 |
|
|
C<$b> verändert werden kann, z.B. wenn C<$b> noch nicht als String |
| 298 |
|
|
vorliegt. |
| 299 |
|
|
|
| 300 |
|
|
Da dies interne Details des Interpreters betrifft, ist es für einen Perl- |
| 301 |
|
|
(Python-, Ruby- ...) Programmierer nicht ersichtlich, wann man ein Lock/Mutex |
| 302 |
|
|
benutzen muss und wann nicht. |
| 303 |
|
|
|
| 304 |
|
|
Hinzu kommt, daß Programmierer von dynamischen Sprachen einen gewissen |
| 305 |
|
|
Schutz erwarten: Eine Exception zur Laufzeit ist akzeptabel, ein Crash |
| 306 |
|
|
oder gar Memory-Corruption ist es nicht. |
| 307 |
|
|
|
| 308 |
|
|
=head3 Lösungen: Perl |
| 309 |
|
|
|
| 310 |
|
|
Threads wurden zum ersten (und letzten) Mal in Perl 5.005 eingeführt. Das |
| 311 |
|
|
5.005-Modell implementierte echte Threads, die, sofern die Platform |
| 312 |
|
|
Kernel-Threads unterstützte, auch parallel liefen. |
| 313 |
|
|
|
| 314 |
|
|
Crashes in diesem Modell waren die Tagesordnung. Offenbar war es |
| 315 |
|
|
Perl-Programmierern nicht zu vermitteln, daß man in zwei Threads nicht |
| 316 |
|
|
gleichzeitig (auch nicht lesend) auf dieselbe Variable zugreifen darf. |
| 317 |
|
|
|
| 318 |
|
|
Dies führte dazu, daß dieses Modell in Verruf geriet, obwohl |
| 319 |
|
|
es eigentlich nur das lieferte, was Threads bedeuten: schwierige |
| 320 |
|
|
Programmierung, Crashes und so weiter. |
| 321 |
|
|
|
| 322 |
|
|
Tatsächlich zeigt dies ein generelles Problem bei Threads auf: Sie lassen |
| 323 |
|
|
sich nicht abstrahieren: Stabile Programme erhält man unter anderem |
| 324 |
|
|
durch Abstraktion und Schichtung: einzelne Aufgaben werden in Komponenten |
| 325 |
|
|
abstrahiert (z.B. Module), die die Details regeln, während der Benutzer |
| 326 |
|
|
dieser Komponenten sich nicht um die Details kümmern braucht. |
| 327 |
|
|
|
| 328 |
|
|
Bei Sicherheit funktioniert dies nicht gut: TLS z.B. ist sehr sicher, |
| 329 |
|
|
dennoch darf man (wie Microsoft schon häufig bewiesen hat) nicht naiv |
| 330 |
|
|
davon ausgehen, daß eine Verbindung abhörsicher oder privat wäre, nur, |
| 331 |
|
|
weil man TLS benutzt. |
| 332 |
|
|
|
| 333 |
|
|
Ähnlich ist es mit Threads: sicherlich kann man Queues und ähnliches |
| 334 |
|
|
abstrahieren, aber in vielen Fällen muss man dennoch wissen, wie diese |
| 335 |
|
|
Komponenten implementiert sind, um z.B. Deadlocks zu vermeiden. Und wenn |
| 336 |
|
|
man Daten an andere Threads sendet, muss man sowieso immer selbst den |
| 337 |
|
|
Zugriff regeln, oder selbst Kopien herstellen. |
| 338 |
|
|
|
| 339 |
|
|
Dies ist grundsätzlich unvereinbar mit dem Anspruch, Threads in |
| 340 |
|
|
Hochsprachen sicher gestalten zu können, es sei denn, man belegt alles |
| 341 |
|
|
mit einem Lock. In der Praxis wird die Implementation dadurch aber sehr |
| 342 |
|
|
viel langsamer, was den Vorteil durch Parallelverarbeitung mehr als |
| 343 |
|
|
aufwiegt. |
| 344 |
root |
1.2 |
|
| 345 |
root |
1.3 |
Das 5.005-Modell verschwand also wieder in der Versenkung, dennoch wollte |
| 346 |
|
|
man irgendwelche "Threads" anbieten. |
| 347 |
root |
1.2 |
|
| 348 |
root |
1.3 |
=head4 Windows - Wurzel allen Übels |
| 349 |
root |
1.2 |
|
| 350 |
root |
1.3 |
Und die "Rettung" war Windows. Genaugenommen die Ineffizienz von Windows. |
| 351 |
root |
1.2 |
|
| 352 |
root |
1.4 |
Dazu muss ich etwas ausholen. Unix (und Perl) haben eine sehr elegante |
| 353 |
|
|
Methode, neue Prozesse zu erzeugen, die man fork+exec nennt. In C (und so |
| 354 |
|
|
ähnlich auch in Perl) sieht das z.B. so aus (ohne Fehlerbehandlung): |
| 355 |
|
|
|
| 356 |
|
|
if (fork () > 0) |
| 357 |
|
|
execl ("/bin/echo", "echo", "Hello, World", (char *)0); |
| 358 |
|
|
|
| 359 |
|
|
Anders als Windows wird das Erzeugen eines neuen Prozesses in zwei Teile |
| 360 |
|
|
zerlegt: zuerst wird eine Kopie des Prozesses erzeugt (fork), dann wird |
| 361 |
|
|
die Kopie durch einen anderen Prozess ersetzt (exec). |
| 362 |
|
|
|
| 363 |
|
|
Das Erstellen einer Kopie ist dank direkter Unterstützung durch die CPU |
| 364 |
|
|
sehr schnell, dennoch mutet es zuerst merkwürdig an. |
| 365 |
|
|
|
| 366 |
|
|
Der Grund, weshalb dies so elegant ist, liegt darin, daß man die Umgebung |
| 367 |
|
|
des neuen Prozesses nicht nur sehr flexibel gestalten kann, sondern |
| 368 |
|
|
auch genau die gleichen Funktionen benutzen kann, die man auch sonst |
| 369 |
|
|
einsetzt. Hier z.B. wird STDIN/STDOUT/SDTERR nach F</dev/null> umgeleitet: |
| 370 |
|
|
|
| 371 |
|
|
if (fork () > 0) |
| 372 |
|
|
{ |
| 373 |
|
|
int fd = open ("/dev/null", O_RDWR); |
| 374 |
|
|
dup2 (fd, 0); dup2 (fd, 1); dup2 (fd, 2); |
| 375 |
|
|
execl ("/bin/echo", "echo", "Hello, World", (char *)0); |
| 376 |
|
|
} |
| 377 |
|
|
|
| 378 |
|
|
Obwohl heutige CPUs dieses Verfahren gut unterstützen, erlaubt Microsoft |
| 379 |
|
|
Windows dies nicht. |
| 380 |
|
|
|
| 381 |
|
|
Stattdessen muss man sehr umständliche funktionen mit zig Parametern |
| 382 |
|
|
aufrufen: |
| 383 |
|
|
|
| 384 |
|
|
CreateProcess (NULL, // No module name (use command line). |
| 385 |
|
|
cmdline, // Command line. |
| 386 |
|
|
NULL, // Process handle not inheritable. |
| 387 |
|
|
NULL, // Thread handle not inheritable. |
| 388 |
|
|
FALSE, // Set handle inheritance to FALSE. |
| 389 |
|
|
0, // No creation flags. |
| 390 |
|
|
NULL, // Use parent's environment block. |
| 391 |
|
|
NULL, // Use parent's starting directory. |
| 392 |
|
|
&si, // Pointer to STARTUPINFO structure. |
| 393 |
|
|
&pi) // Pointer to PROCESS_INFORMATION structure. |
| 394 |
|
|
|
| 395 |
|
|
Tatsächlich sind viele dieser Parameter Zeiger auf Strukturen mit sehr |
| 396 |
|
|
vielen weiteren Parametern. Dies liegt daran, daß Windows praktisch |
| 397 |
|
|
jeden möglichen Konfigurationswunsch im Voraus anbieten muss, da man |
| 398 |
|
|
nicht einfach zwischen Prozesserzeugung und Programmstart irgendwelche |
| 399 |
|
|
Funktionen aufrufen kann. |
| 400 |
|
|
|
| 401 |
|
|
(Irgendwie wird einem dabei auch klar, wieso Windows-Programme von so |
| 402 |
|
|
schlechter Qualität sind, wer soll ich den ganzen Misst auch merken |
| 403 |
|
|
können). |
| 404 |
|
|
|
| 405 |
|
|
Nun gut, die native-Win32-Perls standen also vor dem Problem, daß das |
| 406 |
|
|
elegante fork+exec bei Programmierern sehr beliebt war, ihre Platform dies |
| 407 |
|
|
aber nicht unterstützte. |
| 408 |
|
|
|
| 409 |
|
|
Statt wie bei Cygwin eine saubere (und grausam langsame) fork-Emulation |
| 410 |
|
|
zu implementieren, griff man allerdings bei ActiveState zu einem |
| 411 |
|
|
grausamen (und noch langsamerem) Hack: Bei jedem fork wird der komplette |
| 412 |
|
|
Perl-Interpreter mit I<allen> Variablen kopiert. |
| 413 |
|
|
|
| 414 |
|
|
Damit Perl nicht durcheinander kommt, wird I<jeder> Funktion ein |
| 415 |
|
|
zusätzlicher Parameter übergeben, der angibt, wo man die globalen (bzw. |
| 416 |
|
|
gerade nicht mehr globalen) Variablen findet. Dies bedeutet erheblichen |
| 417 |
|
|
zusätzlichen Aufwand, weshalb Perl sehr viel langsamer wird, natürlich |
| 418 |
|
|
auch dann, wenn man keine Threads benutzt. |
| 419 |
|
|
|
| 420 |
|
|
Hinzu kommt, daß das Kopieren sehr komplex und fehlerbehaftet ist |
| 421 |
|
|
(Crashes sind an der Tagesordnung) und natürlich noch langsamer als blind |
| 422 |
|
|
den gesamten Prozess zu kopieren (wie es Cygwin macht). |
| 423 |
|
|
|
| 424 |
|
|
Das schlimmste jedoch ist, daß diese Fork-Emulation auf einen Schlag |
| 425 |
|
|
praktisch alle XS-Module kaputt macht - bis heute funktionieren die |
| 426 |
|
|
meisten XS-Module deshalb nicht sauber unter Windows. |
| 427 |
|
|
|
| 428 |
|
|
Tragisch ist, daß diese fork-Emulation bis heute nicht richtig |
| 429 |
|
|
funktioniert - Memory Leaks und Crashes sind das Ergebnis. Für Windows |
| 430 |
|
|
wahrhaft angemessen, wahrscheinlich fällt Windows-Programmierern dies |
| 431 |
|
|
gar nicht besonders auf. |
| 432 |
|
|
|
| 433 |
|
|
Solange dies auf Windows beschränkt bleibt, ist das natürlich relativ |
| 434 |
|
|
egal. Leider hatte jemand eine geniale Idee: |
| 435 |
|
|
|
| 436 |
|
|
=head4 Wenn wir Threads benutzen um Prozesse zu emulieren... |
| 437 |
|
|
|
| 438 |
|
|
... dann behaupten wir einfach, unsere Prozesse sind Threads und wir haben |
| 439 |
|
|
Threads. |
| 440 |
|
|
|
| 441 |
|
|
Ich kenne die genauen Details nicht, aber irgendein I<Geisteskranker> |
| 442 |
|
|
fand es cool, alle Perl-Programmierer zu verarschen, Perl überall sehr |
| 443 |
|
|
viel langsamer zu machen und das ganze dann Threads zu nennen: Die |
| 444 |
|
|
Interpreter-Threads waren geboren. |
| 445 |
|
|
|
| 446 |
|
|
Genausowenig, wie Perl kein C ist, obwohl es mit C implementiert ist, sind |
| 447 |
|
|
die Perl-Threads keine Threads, nur weil sie mit Threads implementiert |
| 448 |
|
|
werden. |
| 449 |
|
|
|
| 450 |
|
|
Die sogenannten Perl-Threads sind nichts anderes, als die |
| 451 |
|
|
Windows-Prozess-Emulation auf Unix portiert, mit allen Nachteilen: Nicht |
| 452 |
|
|
nur besitzen die Perl-Pseudo-Threads keinen gemeinsamen Adressraum, sie |
| 453 |
|
|
machen den Perl-Interpreter langsamer und liefern nichts, was jedes |
| 454 |
|
|
Unix nicht wesentlich effizienter mit Hardwareunterstützung erledigen |
| 455 |
|
|
kann. Alle weiteren Effizienzprobleme die durch Kernel-Threads entstehen, |
| 456 |
|
|
sind natürlich auch vertreten. Ohne jeglichen Vorteil aus dem ganzen zu |
| 457 |
|
|
ziehen. |
| 458 |
|
|
|
| 459 |
|
|
Oder doch? Ein genialer Schachzug eigentlich: Man schädigt Perl auf jeder |
| 460 |
|
|
Plattform, auch denjenigen, die dies gar nicht nötig haben, und erhält |
| 461 |
|
|
gleichzeitig ein Buzzword "Thread-Support". |
| 462 |
|
|
|
| 463 |
|
|
Dazu gehört schon Chutzpe. |
| 464 |
|
|
|
| 465 |
|
|
Dies sind die Gründe, weshalb die Perl-Pseudo-Threads sterben sollten: |
| 466 |
|
|
sie verkomplizieren XS-Module unnötig; sind extrem langsam, selbst, wenn |
| 467 |
|
|
man sie gar nicht benutzt; sie funktionieren auf keiner Plattform richtig |
| 468 |
|
|
und vor allem: es sind keine Threads! |
| 469 |
|
|
|
| 470 |
|
|
Wie machen's dann die Anderen? |
| 471 |
|
|
|
| 472 |
|
|
=head3 Python, Ruby und die Anderen |
| 473 |
|
|
|
| 474 |
|
|
Andere Sprachen, die in einer ähnlichen Liga wie Perl spielen, leiden |
| 475 |
|
|
natürlich unter denselben Problemen. |
| 476 |
|
|
|
| 477 |
|
|
Ihre Lösung wird gerne belächelt - Kooperative Threads. |
| 478 |
|
|
|
| 479 |
|
|
Richtig, bei Python läuft nichts parallel. Zumindest sehr wenig. Z.B. |
| 480 |
|
|
I/O. Oder Datenbankzugriffe. |
| 481 |
|
|
|
| 482 |
|
|
Und das sind eigentlich alles die Dinge, die man gerne parallel laufen |
| 483 |
|
|
lassen möchte.... |
| 484 |
|
|
|
| 485 |
|
|
Ruby and die meisten anderen dynamischen Sprachen, die Threads |
| 486 |
|
|
implementieren, machen genau dasselbe. |
| 487 |
|
|
|
| 488 |
|
|
Natürlich ist nicht alles perfekt: Python und Ruby benutzen |
| 489 |
|
|
z.B. Kernel-Threads um Userspace-Threads zu emulieren. Dies ist natürlich |
| 490 |
|
|
ineffizient, dafür aber portabel. |
| 491 |
|
|
|
| 492 |
|
|
=head4 Perl kann's auch - Coro |
| 493 |
|
|
|
| 494 |
|
|
Tatsächlich gibt's für Perl auch echte Threads, nämlich das Coro-Modul, |
| 495 |
|
|
das ebenfalls kooperative Threads implementiert. Anders als Python |
| 496 |
|
|
unterstützt es auch schnelle Userspace-Threads (auf den Plattformen, die |
| 497 |
|
|
nicht völlig kaputt sind, d.h. alles außer den BSDs, wo man aufgrund von |
| 498 |
|
|
libc-Bugs langsame OS-Threads benutzen muss). |
| 499 |
|
|
|
| 500 |
|
|
Tatsächlich gibt es Pläne, eine Art Hybrid-Modell zu implementieren: Auf |
| 501 |
|
|
Perl-Ebene hätte man das Python-Thread-Modell, auf C-Ebene kann man sich |
| 502 |
|
|
aber eben mal einen OS-Thread "borgen", um z.B. asynchrones I/O oder |
| 503 |
|
|
asynchrone Datenbank-Zugriffe zu realisieren, wofür aber support der |
| 504 |
|
|
jeweiligen Module notwendig wird. |
| 505 |
|
|
|
| 506 |
|
|
=head3 Erlang |
| 507 |
|
|
|
| 508 |
|
|
Erlang hat auf den ersten Blick noch ein ganz anderes Thread-Modell, |
| 509 |
root |
1.5 |
nämlich das "echte": "Variablen" sind "geshared" und Threads laufen |
| 510 |
|
|
parallel. |
| 511 |
root |
1.4 |
|
| 512 |
|
|
Allerdings nur auf den ersten Blick: Erlang besitzt gar keine "Variablen", |
| 513 |
|
|
also sind alle Zugriffe erstmal nur Lesend. Außerdem sind die |
| 514 |
|
|
Erlang-Entwickler Profis, was Ausfallsicherheit angeht, und das erreicht |
| 515 |
|
|
man nun mal nur mit Redundanz. Redundanz bedeutet aber auch Abstand, |
| 516 |
|
|
d.h. Erlang-"Threads" müssen auf völlig getrennten Rechnern laufen |
| 517 |
|
|
können. |
| 518 |
|
|
|
| 519 |
|
|
Ergo implementiert Erlang schnelle Userspace-Threads und benutzt für |
| 520 |
|
|
echte Parallelität Prozesse mit nur teilweise gemeinsamen Adressraum, und |
| 521 |
|
|
hat damit alle Vorteile, die man durch Parallelität erreichen kann. |
| 522 |
|
|
|
| 523 |
|
|
Natürlich könnte man die Sprache selbst als gravierenden Nachteil |
| 524 |
|
|
betrachten... Es ist halt nicht Perl. |
| 525 |
|
|
|
| 526 |
|
|
=head2 Schlußwort |
| 527 |
|
|
|
| 528 |
|
|
Zum Wohl aller: Perl-"Threads" müssen sterben. Die Perl-Threads sollen leben! |
| 529 |
root |
1.2 |
|