| 1 |
root |
1.1 |
=head1 NAME |
| 2 |
|
|
|
| 3 |
|
|
EV - (noch) ein Event-Modell für Perl |
| 4 |
|
|
|
| 5 |
|
|
=head1 Was? |
| 6 |
|
|
|
| 7 |
|
|
EV ist ein Perl-Modul, das Ereignisgesteuertes Programmieren |
| 8 |
|
|
unterstützt. Darin unterscheidet es sich erst einmal nicht von anderen |
| 9 |
|
|
dedizierten Modulen wie "Event" oder die in jedes Toolkit (Gtk, Tk etc.) |
| 10 |
|
|
eingebaute Ereignisverarbeitung. |
| 11 |
|
|
|
| 12 |
|
|
EV ist des weiteren eine Schnittstelle zur "Libev"-Bibliothek, eine Art |
| 13 |
|
|
Libevent-Klon, und erbt deshalb deren Features (worin sich EV nun von |
| 14 |
|
|
einigen anderen Modulen unterscheidet): |
| 15 |
|
|
|
| 16 |
|
|
=over 4 |
| 17 |
|
|
|
| 18 |
|
|
=item * Korrektheit |
| 19 |
|
|
|
| 20 |
|
|
Die meisten Ereignis-Bibliotheken funktionieren gut, solange nichts |
| 21 |
|
|
unvorhergesehenes passiert. |
| 22 |
|
|
|
| 23 |
|
|
Die meisten versagen kläglich, wenn die Systemuhr verstellt wird |
| 24 |
|
|
(Zeitsprünge): Aus einem 10s-Timeout können bei Tk (Event, Glib etc.) |
| 25 |
|
|
problemlos mal Jahre werden, aus einem Tag wenige Sekunden. EV unterstützt |
| 26 |
|
|
sowohl relative Timer ("wecke mich in 10 Minuten auf, egal ob ntpd die |
| 27 |
|
|
Uhr ändert oder nicht) als auch absolute Timer ("wecke mich 2008-10-10 |
| 28 |
|
|
12:17:23.445 auf, egal wie lange das wirklich dauert" oder gar "wecke |
| 29 |
|
|
mich zur nächsten Mitternacht auf"), was kein anderes mir bekanntes |
| 30 |
|
|
Event-Modell unterstützt. |
| 31 |
|
|
|
| 32 |
|
|
Registriert man bei Tk (Event::Lib, ...) zwei Watcher auf denselben |
| 33 |
|
|
Filehandle, so kann einer davon verloren gehen (nicht notwendigerweise |
| 34 |
|
|
immer der ältere oder neuere). |
| 35 |
|
|
|
| 36 |
|
|
Stellt man bei Event die "richtigen" Prioritäten ein, kriegt man eine |
| 37 |
|
|
stetig wachsende Event-Queue die nicht kleiner wird (was allerdings selten |
| 38 |
|
|
in Out-Of-Memory-Situationen endet, denn Event wird vorher zu langsam). |
| 39 |
|
|
|
| 40 |
|
|
Das alles sind Kleinigkeiten - bis das Programm deshalb mal "hängt", 100% |
| 41 |
|
|
Rechenzeit verbraucht oder gar sporadisch versagt. |
| 42 |
|
|
|
| 43 |
|
|
Außerdem haben viele Event-Modelle (Event, Glib, Tk...) ein von Perl |
| 44 |
|
|
abweichendes Objektmodell: verliert man beim Event oder Glib die Referenz |
| 45 |
|
|
auf den Watcher, so hat man bestenfalls ein Memory-Leak. Die EV-Watcher |
| 46 |
|
|
bestehen genau so lange, wie Referenzen darauf zeigen wie jedes andere |
| 47 |
|
|
Perl-Objekt auch. |
| 48 |
|
|
|
| 49 |
|
|
Fork ist ein ganz spezielles Problem: wenige Event-Bibliotheken |
| 50 |
|
|
unterstützen es direkt, vor allem diejenigen, die Epoll, Kqueue |
| 51 |
|
|
o.ä. benutzen, da diese Schnittstellen sehr fork-feindlich sind. EV |
| 52 |
|
|
unterstützt fork indem es den Kernel-Zustand im Kindprozess automatisch |
| 53 |
|
|
wiederherstellt, sofern es benutzt wird. |
| 54 |
|
|
|
| 55 |
|
|
=item * Geschwindigkeit |
| 56 |
|
|
|
| 57 |
|
|
EV benutzt "die richtigen" Algorithmen zur Verwaltung: Heaps für die |
| 58 |
|
|
Verwaltung der Timer (keine lineare Liste oder ähnliches wie alle |
| 59 |
|
|
anderen), Arrays, wenn es um viele Watcher geht, Listen, wenn es nur |
| 60 |
|
|
wenige (meist einen) geht. Auch geht Libev nicht vor jedem Pollen alle |
| 61 |
|
|
seine Watcher (Event, Glib, ...) durch um zu sehen ob sie noch alle da |
| 62 |
|
|
sind. |
| 63 |
|
|
|
| 64 |
|
|
Dadurch skaliert EV nicht nur hervorragend in die Bereiche von |
| 65 |
|
|
hunderttausenden Filehandles und Timern (falls man das braucht), sondern |
| 66 |
|
|
ist auch mit wenigen davon sehr schnell (was mindstens genauso wichtig |
| 67 |
|
|
ist). |
| 68 |
|
|
|
| 69 |
|
|
Auch die Perl-Schnittstelle selbst wurde geschrieben, um unnötige |
| 70 |
|
|
Laufzeitkosten zu vermeiden: EV erzeugt einen Watcher immerhin 40 mal |
| 71 |
|
|
schneller als Event und fast viermal so schnell wie Glib, womit EV + |
| 72 |
|
|
AnyEvent immer noch schneller sind als "nacktes" Glib. |
| 73 |
|
|
|
| 74 |
|
|
Auch der Speicherverbrauch ist sehr gering: EV-Watcher sind schlichte |
| 75 |
|
|
geblesste Strings die den Libev-Watcher enthalten. Diese wiederum sind im |
| 76 |
|
|
Vergleich mit anderen Bibliotheken recht klein (weniger als 100 Bytes). |
| 77 |
|
|
|
| 78 |
|
|
=item * Skalierbarkeit |
| 79 |
|
|
|
| 80 |
|
|
Nicht nur die Algorithmen, auch die Schnittstelle zum Betriebssystem muss |
| 81 |
|
|
skalierbar sein, und das bedeutet leider auch: nicht portabel. Jedes |
| 82 |
|
|
Betriebssystem braucht sein eigenes Rezept und daher auch sein eigenes |
| 83 |
|
|
Backend: Libev unterstützt Epoll, Kqueue und Solaris' Event Ports. |
| 84 |
|
|
|
| 85 |
|
|
Einige Backends sind sehr begrenzt (Kqueue z.B. ist auf fast allen System |
| 86 |
|
|
kaputt und supported bestenfalls Sockets) aber in ein anderen Backend |
| 87 |
|
|
integrierbar, so kann man z.B. ein Kqueue-Backend (supported nur Sockets |
| 88 |
|
|
zuverlässig auf FreeBSD) in ein "herkömmliches" Poll-basiertes Backend |
| 89 |
|
|
integrieren. |
| 90 |
|
|
|
| 91 |
|
|
=item * Watcher-Typen |
| 92 |
|
|
|
| 93 |
|
|
Neben den üblichen I/O Watchern und Timern (derer es zwei grundsätzlich |
| 94 |
|
|
verschiedene Typen in EV gibt), bietet EV noch speziellere |
| 95 |
|
|
Watchertypen für POSIX-Signale, Prozess-Watcher, Idle-Watcher (für |
| 96 |
|
|
"Hintergrundprozesse"), Prepare/Check-Watcher, mit denen man EV-fremde |
| 97 |
|
|
Event-Modelle oder -Benutzer integrieren kann und Stat-Watcher, mit denen |
| 98 |
|
|
man Dateisystemobjekte auf Veränderungen überwachen kann. Ganz spezielle |
| 99 |
|
|
Watcher, um C<fork()> zu erkennen oder andere Backends zu integrieren gibt |
| 100 |
|
|
es natürlich auch. |
| 101 |
|
|
|
| 102 |
|
|
=item * C-Schnittstelle |
| 103 |
|
|
|
| 104 |
|
|
Das EV-Modul exportiert die komplette Libev-API auch auf XS-Ebene, was |
| 105 |
|
|
so nur noch Event bietet (bei anderen Modulen muss man auf positive |
| 106 |
|
|
Eigenschaften von Shared-Libraries vertrauen). |
| 107 |
|
|
|
| 108 |
|
|
=back |
| 109 |
|
|
|
| 110 |
|
|
|
| 111 |
|
|
=head1 Warum? |
| 112 |
|
|
|
| 113 |
|
|
EV ist, wie die zugrunde liegende Libev, sehr jung (Ende 2007 erschien |
| 114 |
|
|
die erste Version), und die Entstehung war mehr Zufall als Absicht: Seit |
| 115 |
|
|
ca. 1998 benutze ich regelmäßig und viel das Event-Modul, welches |
| 116 |
|
|
problemlos als das ausgereifteste der verfügbaren Event-Module gelten |
| 117 |
|
|
durfte. |
| 118 |
|
|
|
| 119 |
|
|
Im Laufe der Jahre stieß ich auf eine Menge kleinerer Probleme mit anderen |
| 120 |
|
|
Event-Modellen (siehe weiter oben), doch die Anstrengung, eine eigene |
| 121 |
|
|
Bibliothek zu schreiben, wollte ich nicht aufbringen. |
| 122 |
|
|
|
| 123 |
|
|
Speicherverbrauch war eigentlich nie ein Problem, die Beschränkung auf |
| 124 |
|
|
C<select> und C<poll> schon eher, aber man hat ja schnelle Rechner die |
| 125 |
|
|
das kompensieren (und wenn man nicht drauf achtet, was die Rechenzeit |
| 126 |
|
|
verbraucht, stört es ja sowieso nie). |
| 127 |
|
|
|
| 128 |
|
|
An die echten Fehler in z.B. Event gewöhnt man sich mit der Zeit, bzw. |
| 129 |
|
|
findet mehr oder weniger akzeptable Umleitungen um das Problem, oder |
| 130 |
|
|
vermeidet den Einsatz in solchen Fällen ganz. |
| 131 |
|
|
|
| 132 |
|
|
Eines Tages kam jedoch die Notwendigkeit, wirklich viele Filehandles |
| 133 |
|
|
effizient zu verwalten, und da Libevent sowas wie der Standard für |
| 134 |
|
|
Performance zu sein schien und es dafür keine Perl-Schnittstelle gab |
| 135 |
|
|
(das ist falsch, es gab eine, nämlich C<Event::Lib>, aber ich habe sie |
| 136 |
|
|
trotz suchens auf CPAN nicht wahrgenommen), fing' ich an, eine eigene |
| 137 |
|
|
Schnittstelle zu Libevent zu schreiben. |
| 138 |
|
|
|
| 139 |
|
|
Wenig erfreut war ich, als ich herausfand, daß Libevent die gleichen |
| 140 |
|
|
Probleme wie Tk hat (nicht mehrere Watcher auf einem Filehandle) und auch |
| 141 |
|
|
nicht wenige Schwächen in der API. Das Erlebnis war derart frustrierend, |
| 142 |
|
|
daß ich mich entschieden habe, endgültig mein eigenes Ding' zu machen. Wie |
| 143 |
|
|
erwartet, war es mehr Arbeit, als ich eigentlich investieren wollte, |
| 144 |
|
|
aber nach einigen Monaten hatte ich etwas gebrauchsfähiges, daß sogar |
| 145 |
|
|
bei Benchmarks gegenüber dem "Standard" (nämlich Libevent) hervorragend |
| 146 |
|
|
abschneidet. |
| 147 |
|
|
|
| 148 |
|
|
Da die meisten meiner Module AnyEvent benutzen, und die API von EV (im |
| 149 |
|
|
Geiste...) der von Event ähnelt, war die Umstellung aller meiner wichtigen |
| 150 |
|
|
Projekte (gvpe, rxvt-unicode, Deliantra, mein Webserver sowie mehrere |
| 151 |
|
|
kommerzielle Anwendungen) kein Problem, was natürlich ein sehr guter Test |
| 152 |
|
|
für das Modul war (und ist). |
| 153 |
|
|
|
| 154 |
|
|
EV ist somit das Ergebnis von knapp 10 Jahren Erfahrung mit |
| 155 |
|
|
anderen Event-Modellen, deren Stärken und Schwächen und deren |
| 156 |
|
|
Perl-Schnittstelle: alle mir bekannten Probleme wurden adresssiert. |
| 157 |
|
|
|
| 158 |
|
|
|
| 159 |
|
|
=head1 Wie? |
| 160 |
|
|
|
| 161 |
|
|
Nach so viel Blabla wirds Zeit für Code-Beispiele. |
| 162 |
|
|
|
| 163 |
|
|
Einen langweiligen Timer realisiert man so. |
| 164 |
|
|
|
| 165 |
|
|
use EV; |
| 166 |
|
|
|
| 167 |
|
|
my $timer = EV::timer 1, 2, sub { |
| 168 |
|
|
warn "man rief' mich auf nach 1s dann alle 2s. yeah."; |
| 169 |
|
|
}; |
| 170 |
|
|
|
| 171 |
|
|
EV::loop; |
| 172 |
|
|
|
| 173 |
|
|
Das gibt nach einer Sekunde ein warn, und danach alle zwei Sekunden, bis |
| 174 |
|
|
in alle Ewigkeit/Reset/SIGINT etc. |
| 175 |
|
|
|
| 176 |
|
|
EV::loop kehrt automatisch zurück, wenn es keine aktiven Watcher mehr gibt |
| 177 |
|
|
(das nennt man "Deadlock"): |
| 178 |
|
|
|
| 179 |
|
|
my $delay = EV::timer 1, 0, sub { |
| 180 |
|
|
warn "nur einmal nach 1s werde ich aufgerufen.\n"; |
| 181 |
|
|
}; |
| 182 |
|
|
|
| 183 |
|
|
EV::loop; |
| 184 |
|
|
|
| 185 |
|
|
Das läuft nur für eine Sekunde. Wichtig ist, daß man das Watcher-Objekt, |
| 186 |
|
|
das der Konstruktor liefert, auch aufbewahrt, denn wenn man es vergisst, |
| 187 |
|
|
wird der Watcher automatisch deaktiviert. Hier kehrt C<EV::loop> sofort |
| 188 |
|
|
zurück, wenn keine sonstigen Watcher aktiv sind, denn der Timer wird |
| 189 |
|
|
erzeugt, gestartet und sofort wieder gelöscht: |
| 190 |
|
|
|
| 191 |
|
|
EV::timer 10, 10, sub { }; |
| 192 |
|
|
EV::loop; |
| 193 |
|
|
|
| 194 |
|
|
I/O-Watcher gehen genauso einfach. Das folgende Beispiel "loop"ed so |
| 195 |
|
|
lange, bis man auf STDIN eine Zeile die mit C<q> beginnt, eingibt (z.b. |
| 196 |
|
|
"quit"): |
| 197 |
|
|
|
| 198 |
|
|
my $stdin_watcher = EV::io *STDIN, EV::READ, sub { |
| 199 |
|
|
my $line = <STDIN>; |
| 200 |
|
|
|
| 201 |
|
|
# $_[0] enthält das watcher-objekt |
| 202 |
|
|
$_[0]->stop if $line =~ /^q/; |
| 203 |
|
|
}; |
| 204 |
|
|
|
| 205 |
|
|
EV::loop; |
| 206 |
|
|
|
| 207 |
|
|
Alternativ kann man den Watcher auch "einfach" C<undef>'en (Perl macht das |
| 208 |
|
|
ein bisschen eklig): |
| 209 |
|
|
|
| 210 |
|
|
my $stdin_watcher; |
| 211 |
|
|
$stdin_watcher = EV::io *STDIN, EV::READ, sub { |
| 212 |
|
|
my $line = <STDIN>; |
| 213 |
|
|
|
| 214 |
|
|
undef $stdin_watcher if $line =~ /^q/; |
| 215 |
|
|
}; |
| 216 |
|
|
|
| 217 |
|
|
EV::loop; |
| 218 |
|
|
|
| 219 |
|
|
=head2 Spezialitäten |
| 220 |
|
|
|
| 221 |
|
|
|
| 222 |
|
|
=head1 Und wie noch? |
| 223 |
|
|
|
| 224 |
|
|
=head1 Hä? |
| 225 |
|
|
|
| 226 |
|
|
=back |
| 227 |
|
|
|