| 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 |
|
|
Speicherbereiche in jedem Thread verwendet. Allerdings wird dies durhc |
| 170 |
|
|
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 |
|
|
Pseudothread-Varianten verschwindend gering. |
| 238 |
|
|
|
| 239 |
|
|
=head4 Diskussion |
| 240 |
|
|
|
| 241 |
|
|
Der Benchmark wurde absichtlich so gewählt, daß die Kommunikation einen |
| 242 |
|
|
entscheidenden Bestandteil bildet. |
| 243 |
|
|
|
| 244 |
|
|
Die Pseudo-Threads von Perl sind hierbei doppelt im Nachteil: einerseits |
| 245 |
|
|
besitzen sie keinen gemeinsamen Adressraum (d.h. Variablen müssen kopiert |
| 246 |
|
|
werden), anderseits ist es durch Threads implementiert, und hat dadurch |
| 247 |
|
|
einen erhöhten Aufwand bei der Verwaltung (doppelte Indirektion). |
| 248 |
|
|
|
| 249 |
|
|
Dies allein kann den gewaltigen Unterschied von 31595% (!) erklären. |
| 250 |
|
|
|
| 251 |
|
|
Der Unterschied zwischen den beiden Pseudothreaded-Kurven ist schwerer zu |
| 252 |
|
|
erklären: Da die Programme identisch sind, muss der Unterschied durch |
| 253 |
|
|
andere Effekte erklärt werden: Die Kommunikation läuft in einem Fall |
| 254 |
|
|
weitestgehend über den CPU-Cache, während dieser bei mehrere Cores |
| 255 |
|
|
nicht benutzt werden kann, hier also die Kommunikation über den L2-Cache |
| 256 |
|
|
(ca. 10 mal langsamer) oder den Hauptspeicher (ca. 50 mal langsamer) |
| 257 |
|
|
abläuft. |
| 258 |
|
|
|
| 259 |
|
|
Hinzu kommt, daß während der Kommunikation gemeinsam genutzte Variablen |
| 260 |
|
|
gegen Zugriffe anderer CPUs geschützt werden müssen. Versuchen |
| 261 |
|
|
mehrere Threads gleichzeitig auf diese zuzugreifen, müssen sie sich |
| 262 |
|
|
synchronisieren. Auch dies läuft über den Hauptspeicher ab und ist noch |
| 263 |
|
|
dazu wesentlich aufwändiger. |
| 264 |
|
|
|
| 265 |
|
|
Der Unterschied von ca. 30% bei den beiden Coro-Varianten erklärt |
| 266 |
|
|
sich alleine dadurch, daß ein Pseudo-Threaded-Perl wesentlich mehr |
| 267 |
|
|
Aufwand treiben muss, um die Adressräume der Threads künstlich |
| 268 |
|
|
auseinanderzuhalten, mithin dupliziert es die Arbeit der MMU die in der |
| 269 |
|
|
CPU eingebaut ist und, obwohl Perl effizienter ist, ist eine Software-MMU |
| 270 |
|
|
natürlich immer noch langsamer als eine gut optimierte Hardwarelösung. |
| 271 |
|
|
|
| 272 |
|
|
|
| 273 |
|
|
=head2 Die Herausforderung: Threads in dynamischen Sprachen |
| 274 |
|
|
|
| 275 |
|
|
Thread-Programmierung ist komplex, vor allem, wenn die verwendeten Threads |
| 276 |
|
|
preemptiv sind oder sogar echt parallel arbeiten. Ein gutes Beispiel ist |
| 277 |
|
|
das "double-checked-locking"-Pattern, daß zehn Jahre in jedem Fachbuch |
| 278 |
|
|
stand, bis man feststellte, daß es nicht stabil arbeitet. |
| 279 |
|
|
|
| 280 |
|
|
Für "hardwarenahe" Sprachen ist es sehr viel einfacher, mit Threads |
| 281 |
|
|
umzugehen: viele Datentypen (z.B. C<int> in C) erlauben atomare Zugriffe, |
| 282 |
|
|
Locking ist explizit, der Zugriff selbst (nur lesend oder verändernd) ist |
| 283 |
|
|
genau festgelegt und so weiter. Und wenn's Crasht dann Crasht's eben - in |
| 284 |
|
|
C ist das nichts ungewöhnliches. |
| 285 |
|
|
|
| 286 |
|
|
Für Sprachen wie Perl oder Python stellt sich das ganze anders |
| 287 |
|
|
dar: Selbst "einfache" Integer-Variablen sind auf C-Ebene durch viele |
| 288 |
|
|
Variablen repräsentiert. Dadurch gibt es keine atomaren Zugriffe, d.h. |
| 289 |
|
|
auch lesend zugreifen kann man nur indem man ein Lock benutzt. Hinzu |
| 290 |
|
|
kommt, daß viele Zugriffe, die man als Programmierer als "Lesezugriff" |
| 291 |
|
|
ansieht, intern tatsächlich Schreibzugriffe auslöst. Ein Beispiel: |
| 292 |
|
|
|
| 293 |
|
|
$a .= $b; |
| 294 |
|
|
|
| 295 |
|
|
Ganz klar, C<$a> wird verändert. Nicht so offensichtlich ist, daß auch |
| 296 |
|
|
C<$b> verändert werden kann, z.B. wenn C<$b> noch nicht als String |
| 297 |
|
|
vorliegt. |
| 298 |
|
|
|
| 299 |
|
|
Da dies interne Details des Interpreters betrifft, ist es für einen Perl- |
| 300 |
|
|
(Python-, Ruby- ...) Programmierer nicht ersichtlich, wann man ein Lock/Mutex |
| 301 |
|
|
benutzen muss und wann nicht. |
| 302 |
|
|
|
| 303 |
|
|
Hinzu kommt, daß Programmierer von dynamischen Sprachen einen gewissen |
| 304 |
|
|
Schutz erwarten: Eine Exception zur Laufzeit ist akzeptabel, ein Crash |
| 305 |
|
|
oder gar Memory-Corruption ist es nicht. |
| 306 |
|
|
|
| 307 |
|
|
=head3 Lösungen: Perl |
| 308 |
|
|
|
| 309 |
|
|
Threads wurden zum ersten (und letzten) Mal in Perl 5.005 eingeführt. Das |
| 310 |
|
|
5.005-Modell implementierte echte Threads, die, sofern die Platform |
| 311 |
|
|
Kernel-Threads unterstützte, auch parallel liefen. |
| 312 |
|
|
|
| 313 |
|
|
Crashes in diesem Modell waren die Tagesordnung. Offenbar war es |
| 314 |
|
|
Perl-Programmierern nicht zu vermitteln, daß man in zwei Threads nicht |
| 315 |
|
|
gleichzeitig (auch nicht lesend) auf dieselbe Variable zugreifen darf. |
| 316 |
|
|
|
| 317 |
|
|
Dies führte dazu, daß dieses Modell in Verruf geriet, obwohl |
| 318 |
|
|
es eigentlich nur das lieferte, was Threads bedeuten: schwierige |
| 319 |
|
|
Programmierung, Crashes und so weiter. |
| 320 |
|
|
|
| 321 |
|
|
Tatsächlich zeigt dies ein generelles Problem bei Threads auf: Sie lassen |
| 322 |
|
|
sich nicht abstrahieren: Stabile Programme erhält man unter anderem |
| 323 |
|
|
durch Abstraktion und Schichtung: einzelne Aufgaben werden in Komponenten |
| 324 |
|
|
abstrahiert (z.B. Module), die die Details regeln, während der Benutzer |
| 325 |
|
|
dieser Komponenten sich nicht um die Details kümmern braucht. |
| 326 |
|
|
|
| 327 |
|
|
Bei Sicherheit funktioniert dies nicht gut: TLS z.B. ist sehr sicher, |
| 328 |
|
|
dennoch darf man (wie Microsoft schon häufig bewiesen hat) nicht naiv |
| 329 |
|
|
davon ausgehen, daß eine Verbindung abhörsicher oder privat wäre, nur, |
| 330 |
|
|
weil man TLS benutzt. |
| 331 |
|
|
|
| 332 |
|
|
Ähnlich ist es mit Threads: sicherlich kann man Queues und ähnliches |
| 333 |
|
|
abstrahieren, aber in vielen Fällen muss man dennoch wissen, wie diese |
| 334 |
|
|
Komponenten implementiert sind, um z.B. Deadlocks zu vermeiden. Und wenn |
| 335 |
|
|
man Daten an andere Threads sendet, muss man sowieso immer selbst den |
| 336 |
|
|
Zugriff regeln, oder selbst Kopien herstellen. |
| 337 |
|
|
|
| 338 |
|
|
Dies ist grundsätzlich unvereinbar mit dem Anspruch, Threads in |
| 339 |
|
|
Hochsprachen sicher gestalten zu können, es sei denn, man belegt alles |
| 340 |
|
|
mit einem Lock. In der Praxis wird die Implementation dadurch aber sehr |
| 341 |
|
|
viel langsamer, was den Vorteil durch Parallelverarbeitung mehr als |
| 342 |
|
|
aufwiegt. |
| 343 |
root |
1.2 |
|
| 344 |
root |
1.3 |
Das 5.005-Modell verschwand also wieder in der Versenkung, dennoch wollte |
| 345 |
|
|
man irgendwelche "Threads" anbieten. |
| 346 |
root |
1.2 |
|
| 347 |
root |
1.3 |
=head4 Windows - Wurzel allen Übels |
| 348 |
root |
1.2 |
|
| 349 |
root |
1.3 |
Und die "Rettung" war Windows. Genaugenommen die Ineffizienz von Windows. |
| 350 |
root |
1.2 |
|
| 351 |
|
|
|