ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/docs/pws2006/event.pod
Revision: 1.3
Committed: Sat Jan 14 04:20:02 2006 UTC (20 years, 8 months ago) by root
Branch: MAIN
Changes since 1.2: +192 -3 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
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