ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/docs/pws2009/threads.pod
Revision: 1.5
Committed: Sat Jan 17 06:53:25 2009 UTC (17 years, 9 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.4: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

# Content
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 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 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 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 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 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 =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 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 durch
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 =head3 Ein Benchmark: Matrixmultiplikation
184
185 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
193 Das Programm findet sich unter F<http://data.plan9.de/matmult>, ist aber
194 für die Diskussion nicht relevant.
195
196 =begin latex
197
198 \begin{center}
199 \includegraphics{mat2.eps}
200 \end{center}
201
202 =end latex
203
204 =head4 Beschreibung
205
206 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 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 (dies ist ein AMD64-Perl, ein
238 x86-Perl hat weitaus höhere Overheads).
239
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
345 Das 5.005-Modell verschwand also wieder in der Versenkung, dennoch wollte
346 man irgendwelche "Threads" anbieten.
347
348 =head4 Windows - Wurzel allen Übels
349
350 Und die "Rettung" war Windows. Genaugenommen die Ineffizienz von Windows.
351
352 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 nämlich das "echte": "Variablen" sind "geshared" und Threads laufen
510 parallel.
511
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