| 1 |
root |
1.1 |
=head1 Ereignisgesteuerte Programmierung mit Perl - die Realität |
| 2 |
|
|
|
| 3 |
|
|
=head2 Zusammenfassung |
| 4 |
|
|
|
| 5 |
|
|
Ereignisgesteuerte Programmierung ist hinreichend bekannt. Trotzdem |
| 6 |
|
|
wird sie relativ selten benutzt - in Modulen so gut wie garnicht. Ich |
| 7 |
|
|
möchte hier verschiedene Arten der Ereignissteuerung sauber von anderen |
| 8 |
|
|
Formen der Programmierung zu trennen, vorhandene Ereignismodule und ihre |
| 9 |
|
|
Unterschiede vorstellen. Vor allem sollen Möglichkeiten gezeigt werden, |
| 10 |
|
|
wie man als Modulautor - je nach gewünschtem Aufwand - verschiedene APIs |
| 11 |
|
|
anbieten kann. |
| 12 |
|
|
|
| 13 |
root |
1.2 |
|
| 14 |
|
|
=head1 Ereignissteuerung - In aller Kürze |
| 15 |
|
|
|
| 16 |
|
|
Ereignissteuerung ist - für mich - kein sehr exakt festgelegter Begriff |
| 17 |
|
|
oder eine festgelegte Syntax. Daher will ich einige Beispiele für |
| 18 |
|
|
verschiedene Arten bzw. Grade der Ereignissteuerung, wie sie in der Praxis |
| 19 |
|
|
vorkommen. |
| 20 |
|
|
|
| 21 |
|
|
Üblicherweise verwendet man Ereignissteuerung immer dann, wenn man |
| 22 |
|
|
genügend Rechenzeit für die Verarbeitung vieler Aufgaben hat, jedoch durch |
| 23 |
|
|
externe Umstände (I/O-Geschwindigkeit, Netzwerk, User klickt nicht schnell |
| 24 |
|
|
genug :) gezwungen ist, auf Daten bzw. Ereignisse zu warten. |
| 25 |
|
|
|
| 26 |
|
|
Ereignissteuerung bedeutet dann, ein Programm derart zu strukturieren, |
| 27 |
|
|
daß es auf Ereignisse wartet und - möglichst schnell - reagiert. Die |
| 28 |
|
|
Haupt-Eingabe besteht sozusagen in einem Strom von Ereignissen |
| 29 |
|
|
(Event-Stream). |
| 30 |
|
|
|
| 31 |
|
|
Unter Unix gibt es eine zentrale Funktion, mit dem ein Programm auf |
| 32 |
|
|
Ereignisse warten kann: C<select> (bzw. auch C<pselect>/C<poll>, die sich |
| 33 |
|
|
aber nicht von C<select> unterscheiden, sowie kompliziertere Methoden wie |
| 34 |
|
|
realtime-Signale, die aber alle auf dasselbe hinauslaufen). C<select> kann |
| 35 |
|
|
auf einen Timeout und auf die Möglichkeit, auf einen Filehandle schreiben |
| 36 |
|
|
oder davon lesen zu können, warten. Dies reicht für alles aus, da unter |
| 37 |
|
|
Unix "alles ein Dateihandle ist" (und wenn dies einmal nicht der Fall ist, |
| 38 |
|
|
ist das gleich ein major pain in the ass und man tut gut daran, es auf |
| 39 |
|
|
einen Dateihandle abzubilden). |
| 40 |
|
|
|
| 41 |
|
|
Daher ist eine derartige Programmstruktur meist eine zentrale Schleife, |
| 42 |
|
|
in der auf Ereignisse (definiert durch "Watcher") gewartet wird und |
| 43 |
|
|
dann Funktionen aufgerufen werden, um auf diese Ereignisse zu reagieren |
| 44 |
|
|
("Callbacks"). Da C<select> etwas low-level ist, gibt es Bibliotheken |
| 45 |
|
|
und Module, die diese Funktionalität in eine freundlichere Schnittstelle |
| 46 |
|
|
gießen - das nennt man dan "Event-Modell". |
| 47 |
|
|
|
| 48 |
|
|
Daraus folgt, daß das "Hauptprogramm" der Aufruf dieser Schleife/dieses |
| 49 |
|
|
Event-Modells ist, und alle Ereignisse in Unterfunktionen behandelt werden |
| 50 |
root |
1.3 |
müssen. Daher lassen sich in einem Programm auch sehr schwer mehrere |
| 51 |
root |
1.2 |
Event-Modelle miteinander mischen, da nur jeweils eines die Kontrolle über |
| 52 |
|
|
den Programmablauf haben kann. |
| 53 |
|
|
|
| 54 |
|
|
|
| 55 |
root |
1.3 |
=head1 Formen der Ereignissteuerung |
| 56 |
root |
1.1 |
|
| 57 |
root |
1.3 |
=head2 Lineare Ablauf - "Nicht Ereignisgesteuert" |
| 58 |
|
|
|
| 59 |
|
|
Wir alle kennen (hoffe ich) LWP, mit dem man sehr einfach Webseiten u.ä. |
| 60 |
|
|
abrufen kann: |
| 61 |
|
|
|
| 62 |
|
|
use LWP::Simple; |
| 63 |
|
|
print get "http://www.cpan.org/"; |
| 64 |
|
|
|
| 65 |
|
|
Ein heutiger Rechner kann das in Millisekunden erledigen - wenn es länger |
| 66 |
|
|
dauert, liegt es nicht am eigenen Rechner sondern am Netz. LWP arbeiette |
| 67 |
|
|
intern nämlich folgendermaßen: |
| 68 |
|
|
|
| 69 |
|
|
$socket = new IO::Socket ...; |
| 70 |
|
|
print $socket $request; |
| 71 |
|
|
$response = read $socket; |
| 72 |
|
|
return $response; |
| 73 |
|
|
|
| 74 |
|
|
Die Wartezeit entsteht vor allem beim Lesen der Antwort, und zu kleineren |
| 75 |
|
|
Teilen beim Verbindungsaufbau und selten beim Schreiben der Anforderung. |
| 76 |
|
|
|
| 77 |
|
|
Prinzipiell gibt es auch hier eine Folge von Ereignissen, auf die gewartet |
| 78 |
|
|
wird - allerdings immer nur eines, d.h. die Ereignisse "steuern" den |
| 79 |
|
|
Programmablauf nicht. Daher ist dies keine Ereignissteuerung. |
| 80 |
|
|
|
| 81 |
|
|
Der Vorteil einer solchen Struktur ist ihre Einfachheit - die zeitliche |
| 82 |
|
|
und logische Abfolge des Requests stehen klar hintereinander. |
| 83 |
|
|
|
| 84 |
|
|
Diese Einfachheit läßt sich auch ereignisgesteuert erreichen - allerdings |
| 85 |
|
|
mit beträchtlichem Mehraufwand an anderer Stelle, den man jedoch gut in |
| 86 |
|
|
andere Module verpacken kann. |
| 87 |
root |
1.1 |
|
| 88 |
|
|
=head3 blocking vs. blocking |
| 89 |
|
|
|
| 90 |
root |
1.3 |
Ein paar Worte noch zu blocking vs. non-blocking, bzw. blocking vs. |
| 91 |
|
|
andere Arten von blocking: Unter Unix kann man Filehandles in den |
| 92 |
|
|
"non-blocking"-Zustand versetzen. Dies ist manchmal hilfreich, hat aber |
| 93 |
|
|
mit Ereignissteuerung wenig zu tun: Zum Lesen von Sockets braucht man |
| 94 |
|
|
dies nicht, und bei Dateien zeigt es keine Wirkung (es wirkt nur auf |
| 95 |
|
|
Dateihandles, bei denen die Menge an Daten unbekannt ist). In vielen |
| 96 |
|
|
Fällen ist es sogar hinderlich. |
| 97 |
|
|
|
| 98 |
|
|
So ist es bei vielen Protokollen (whois, finger, viele HTTP-Anfragen) |
| 99 |
|
|
fast sicher, daß die Anfrage komplett in den TCP-Buffer geschrieben |
| 100 |
|
|
werden kann. Passt er nicht, blockiert der Prozess, aber wenn dies |
| 101 |
|
|
unter normalen Umständen nicht vorkommt, ist dies meistens akzeptabler |
| 102 |
|
|
als die Programmstruktur nur für diesen zu verkomplizieren, wie dies |
| 103 |
|
|
bei einem non-blocking Dateihandle nötig wäre (leider erzeugt das |
| 104 |
|
|
C<IO::Socket>-Modul alle Socket-Handles per default non-blocking, was |
| 105 |
|
|
meiner Meinung nach mehr Probleme schafft als löst). |
| 106 |
|
|
|
| 107 |
|
|
=head2 State-Maschinen |
| 108 |
|
|
|
| 109 |
|
|
Um nun auf Ereignisse in beliebiger Folge reagieren zu können, muss man |
| 110 |
|
|
jedes Ereignis ("Verbindung aufgebaut", "Daten können geschrieben werden", |
| 111 |
|
|
"Daten können gelesen werden") in einer Unterroutine abarbeiten. |
| 112 |
|
|
|
| 113 |
|
|
Bei Frameworks wie POE werden alle diese Unterroutinen als Methoden eines |
| 114 |
|
|
Objektes aufgefasst. Das Objekt speichert den Zustand (z.B. "Aufbauphase", |
| 115 |
|
|
"Antwortphase"). Bei jedem Ereignis muss der Zustand aus dem Objekt |
| 116 |
|
|
"deserialisiert" werden, die Methode aufgerufen werden und der neue |
| 117 |
|
|
Zustand wieder serialisiert werden. |
| 118 |
|
|
|
| 119 |
|
|
Für das Finger-Protokoll würde das in etwa so aussehen (völlig fiktive |
| 120 |
|
|
Syntax): |
| 121 |
|
|
|
| 122 |
|
|
# wird aufgerufen, wenn Daten geschrieben werden können |
| 123 |
|
|
sub can_write { |
| 124 |
|
|
my ($self) = @_; |
| 125 |
|
|
|
| 126 |
|
|
# request blockierend schreiben, da klein |
| 127 |
|
|
print {$self->{fh}} $self->{username}; |
| 128 |
|
|
# sorge dafür, daß beim nächsten mal can_Read aufgerufen wird |
| 129 |
|
|
$self->next_event ($self->{fh}, "readable", "can_read"); |
| 130 |
|
|
} |
| 131 |
|
|
|
| 132 |
|
|
# wird aufgerufen, wenn daten vorliegen |
| 133 |
|
|
sub can_read { |
| 134 |
|
|
my ($self) = q_; |
| 135 |
|
|
|
| 136 |
|
|
sysread $self->{fh}, $self->{response}, length $self->{response}, 8192 |
| 137 |
|
|
or $self->next_event (undef, undef, "finish"); |
| 138 |
|
|
} |
| 139 |
|
|
|
| 140 |
|
|
C<can_write> schreibt den Request und sorgt dann dafür, daß der Zustand |
| 141 |
|
|
"can_read" angenommen wird, bei dem die Methodedesselben Namens aufgerufen |
| 142 |
|
|
wird, sobald Daten vom Dateihandle gelesen werden können. |
| 143 |
|
|
|
| 144 |
|
|
C<can_read> liest nun die Daten ein und verbleibt im aktuellen Zustand, |
| 145 |
|
|
solange Daten gelesen werden können. Bei Fehlern oder bei Verbindungsende |
| 146 |
|
|
wird in den Zustand "finish" gesprungen. Typischwerweise gibt es noch |
| 147 |
|
|
andere Methoden, die z.B. bei Fehlern aufgerufen werden können. |
| 148 |
|
|
|
| 149 |
|
|
Persönlich finde ich diesen Stil äußerst masochistisch und verwirrend, |
| 150 |
|
|
aber es soll Programmierer geben, die anders denken. Für ich zählt aber |
| 151 |
|
|
vor allem die Menge an Quellcode, die erzeugt und gewartet werden muss, |
| 152 |
|
|
sowie das weite Auseinanderliegen wichtiger Stellen - der Ablauf ist nicht |
| 153 |
|
|
klar. |
| 154 |
|
|
|
| 155 |
|
|
Trotzdem ist dieser Stil häufig vertreten - meistens nicht in dieser |
| 156 |
|
|
Klarheit sondern in Kombination mit anderen Stilen (im obigen Beispiel |
| 157 |
|
|
wird z.B. die Anfrage nicht Ereignisgesteuert geschrieben) - in der |
| 158 |
|
|
Realität muss man zwischen Klarheit, Geschwindigkeit, Abhängigkeiten und |
| 159 |
|
|
virelem mehr abwägen! |
| 160 |
|
|
|
| 161 |
root |
1.1 |
=head2 Continuation Passing |
| 162 |
|
|
|
| 163 |
root |
1.3 |
Ein etwas weniger häufig benutzter Stil ist Continuation Passing. Die |
| 164 |
|
|
Ursache für das seltenere Vorkommen liegt I<meiner Meinung> nach darin, |
| 165 |
|
|
daß dies in anderen (verbreiteten) Programmiersprachen (C, Python, ...) |
| 166 |
|
|
weniger oder garnicht gut realisierbar ist, bzw. dies etwas Verständnis |
| 167 |
|
|
und Wissen erfordert, was heutzutage nicht mehr gefördert wird. |
| 168 |
|
|
|
| 169 |
|
|
Wie dem auch sei, hier ein Beispiel wie man eine Datei öffnet und einige |
| 170 |
|
|
Daten liest, und sie dann löscht, mit IO::AIO und voller Fehlerbehandlung: |
| 171 |
|
|
|
| 172 |
|
|
aio_open "tempfile", O_RDONLY, 0, sub { |
| 173 |
|
|
my $fh = shift |
| 174 |
|
|
or die "tempfile: $!"; |
| 175 |
|
|
|
| 176 |
|
|
my $buf; |
| 177 |
|
|
aio_read $fh, 0, 8192, $buf, 0, sub { |
| 178 |
|
|
$_[0] > 0 # keine leeren Files |
| 179 |
|
|
or die "tempfile: $!"; |
| 180 |
|
|
|
| 181 |
|
|
aio_unlink "tempfile", sub { |
| 182 |
|
|
$_[0] and die "tempfile: $!"; |
| 183 |
|
|
|
| 184 |
|
|
print "successfully up to 8192 bytes, and nuked file.\n"; |
| 185 |
|
|
}; |
| 186 |
|
|
}; |
| 187 |
|
|
}; |
| 188 |
|
|
|
| 189 |
|
|
Das Schema ist immer das gleiche: Bei jeder Anforderung (open/read/unlink) |
| 190 |
|
|
wird eine Closure übergeben (die Zustandsdaten und Reaktion verkörpert), |
| 191 |
|
|
die nach Beendigung der Anforderung aufgerufen wird. C<aio_open> kehrt |
| 192 |
|
|
sofort zurück, aber die Closure wird erst dann aufgerufen, wenn die Datei |
| 193 |
|
|
geöffnet wurde oder ein Fehler auftrat. |
| 194 |
|
|
|
| 195 |
|
|
Der Name dieses Stils kommt daher, daß bei jeder Anforderung die komplette |
| 196 |
|
|
"Fortsetzung" (Continuation) des Programmablaufes mit übergeben wird. |
| 197 |
|
|
|
| 198 |
|
|
Der Vorteil des Stils liegt darin, daß die logische (nicht zeitliche) |
| 199 |
|
|
lineare Reihenfolge klar bleibt und alles nahe beieinander |
| 200 |
|
|
steht. Nachteil ist das zunehmende Nesting und die weniger flexiblen |
| 201 |
|
|
Reaktionsmöglichkeiten. Außerdem bietet sich nicht jede Art der |
| 202 |
|
|
Anforderung gleichermaßen an: IO::AIO fördert diesen Stil, bei Event wird |
| 203 |
|
|
es meistens etwas umständlicher, da mehr Parameter zu übergeben sind und |
| 204 |
|
|
die Watcher nach jedem Ereignis per Hand gelöscht werden müssen. |
| 205 |
|
|
|
| 206 |
|
|
Beides läßt sich umgehen, indem man sich in Richtung State-Maschine |
| 207 |
|
|
bewegt: |
| 208 |
|
|
|
| 209 |
|
|
sub do_open { |
| 210 |
|
|
aio_open ..., sub { |
| 211 |
|
|
do_read (@_); |
| 212 |
|
|
}; |
| 213 |
|
|
} |
| 214 |
|
|
|
| 215 |
|
|
sub do_read { |
| 216 |
|
|
my ($fh) = @_; |
| 217 |
|
|
|
| 218 |
|
|
aio_read ..., sub { |
| 219 |
|
|
do_unlink ($fh, @_); |
| 220 |
|
|
}; |
| 221 |
|
|
} |
| 222 |
|
|
|
| 223 |
|
|
sub do_unlink { |
| 224 |
|
|
... |
| 225 |
|
|
} |
| 226 |
|
|
|
| 227 |
|
|
Das ist erstmal umständlicher, aber sehr langen Closures manchmal |
| 228 |
|
|
vorzuziehen. Der Zustand wird hier nicht in einem Objekt gespeichert, |
| 229 |
|
|
sondern in den Closures (z.B. C<$fh> bei C<aio_read>). |
| 230 |
|
|
|
| 231 |
|
|
=head2 Coroutinen/Threads/Prozesse |
| 232 |
|
|
|
| 233 |
|
|
Der letzte Stil besteht darin, für jeden logischen Ablauf eine Coroutinem |
| 234 |
|
|
einen Thread oder einen Prozess zu benutzen. Die Nachteile nenne ich |
| 235 |
|
|
gleich: Coroutinen sind nicht in Perl integriert und daher etwas |
| 236 |
|
|
unportabler als Perl selbst, Threads stehen in Perl überhaupt nicht zur |
| 237 |
|
|
Verfügung, und Prozesse sind relativ große Dinger, von denen man sich |
| 238 |
|
|
nicht wirklich viele halten möchte. |
| 239 |
|
|
|
| 240 |
|
|
Das obige C<IO::AIO>-Beispiel sähe mit Hilfe von C<Coro::AIO> so aus: |
| 241 |
|
|
|
| 242 |
|
|
my $fh = aio_open "tempfile", O_RDONLY, 0 |
| 243 |
|
|
or die "tempfile: $!"; |
| 244 |
|
|
aio_read $fh, 0, 8192, my $buf, 0 |
| 245 |
|
|
or die "tempfile empty or read error ($!)"; |
| 246 |
|
|
aio_unlink "tempfile"; |
| 247 |
|
|
|
| 248 |
|
|
Meiner Meinung nach ist dies die klarste Methode, eine komplett |
| 249 |
|
|
Ereignisgesteuerte Abläufe darzustellen. Der Overhead der durch die |
| 250 |
|
|
Verwaltung von Coroutinen entsteht, ist allerdings höher als der anderer |
| 251 |
|
|
Ansätze (allerdings niedriger als bei Threads oder Prozesse). |
| 252 |
root |
1.1 |
|
| 253 |
|
|
|
| 254 |
|
|
=head1 Event-Modelle |
| 255 |
|
|
|
| 256 |
|
|
=head2 Event |
| 257 |
|
|
|
| 258 |
|
|
=head2 Glib/Gtk2, Tk, |
| 259 |
|
|
|
| 260 |
|
|
=head2 Coroutinen - Coro::AIO |
| 261 |
|
|
|
| 262 |
|
|
|
| 263 |
|
|
=head1 Das Problem für Modulautoren |
| 264 |
|
|
|
| 265 |
|
|
|
| 266 |
|
|
=head1 Lösungen/Workarounds |
| 267 |
|
|
|
| 268 |
|
|
=head2 Wozu? Blocking reicht - LWP |
| 269 |
|
|
|
| 270 |
|
|
=head2 Überlass es dem User - Net::Knuddels, IO::AIO |
| 271 |
|
|
|
| 272 |
|
|
=head2 Leg dich fest - Coro::Event |
| 273 |
|
|
|
| 274 |
|
|
=head2 Bilde ein Modell auf ein anderes ab - Glib::Event |
| 275 |
|
|
|
| 276 |
|
|
=head2 Threads - ??? |
| 277 |
|
|
|
| 278 |
|
|
=head2 AnyEvent - Coro::AIO, Net::FCP |
| 279 |
|
|
|
| 280 |
|
|
|
| 281 |
root |
1.2 |
=head1 Fallbeispiel Modul-API: Net-FCP und AnyEvent |
| 282 |
root |
1.1 |
|
| 283 |
root |
1.2 |
=head2 Benutzung |
| 284 |
root |
1.1 |
|
| 285 |
root |
1.2 |
=head2 Implementation |
| 286 |
root |
1.1 |
|