1 |
=encoding utf-8 |
2 |
|
3 |
=head1 IO::AIO - Asynchronous Input/Output |
4 |
|
5 |
=head2 Motivation |
6 |
|
7 |
In vielen Situationen wäre es schön (oder ist man sogar gezwungen), |
8 |
bestimmte Reaktionszeiten einzuhalten: Ein Web-Server, der viele Sockets |
9 |
bedient, sollte sich möglichst rechtzeitig um diese kümmern können. Ein |
10 |
interktives Spiel muss bestimmte Antwortzeiten einhalten. Und manchmal |
11 |
wäre es einfach nur hilfreich, wenn man etwas anderes tun könnte, |
12 |
während der Rechner auf die Festplatte wartet. |
13 |
|
14 |
Unix bietet herkömmlicherweise relativ wenig Unterstützung für |
15 |
derartige Anliegen. Daher habe ich das Modul IO::AIO entwickelt, mit dem |
16 |
sich alle wichtigen I/O-Operationen asynchron abwickeln lassen. |
17 |
|
18 |
=head2 Asynchron mit Synchronisation |
19 |
|
20 |
Asynchron bedeutet, daß die eigentliche Ein-/Ausgabe, z.B. das schreiben |
21 |
einer Datei, gleichzeitig mit anderen Aktivitäten eines Programmes |
22 |
geschieht. Dies unterscheidet asynchrone I/O von non-blocking I/O, |
23 |
bei letzterem wird das Programm zwar ebenfalls nicht behindert, die |
24 |
eigentliche Ein-/Ausgabe findte jedoch immer noch synchron statt. |
25 |
|
26 |
Beispiel: |
27 |
|
28 |
aio_readdir "/etc", sub { |
29 |
my $entries = shift |
30 |
or die "error while reading /etc: $!"; |
31 |
print "read /etc, entries: @$entries\n"; |
32 |
}; |
33 |
|
34 |
Dieser Aufruf erzeugt eine readdir-Anfrage, die asynchron bearbeitet |
35 |
wird, d.h. C<aio_readdir> kehrt sofort zurück. Sobald das Verzeichnis |
36 |
eingelesen wurde, wird der übergebene Callback mit den Einträgen |
37 |
aufgerufen. |
38 |
|
39 |
Da Perl nicht einfach jederzeit unterbrochen werden kann (bzw. man dies |
40 |
auch garnicht zu jedem Zeitpunkt möchte), geschieht die Verarbeitung |
41 |
von Resultaten in IO::AIO synchron, d.h. die eigentliche Ein-/Ausgabe |
42 |
findet asynchron statt, aber der Callback, der das Ende der Operation |
43 |
signalisiert, wird nur zu bestimmten Zeiten aufgerufen, und zwar immer |
44 |
dann, wenn das Perl-Programm IO::AIO::poll_cb aufruft. |
45 |
|
46 |
Wie man dies tut ist relativ unwichtig. Wann dagegen ist wichtig, und um |
47 |
die Integration von IO::AIO in bestehende Event-Modelle zu erleichtern, |
48 |
stellt IO::AIO einen filedeskriptor zur Verfügung: sobald dieser lesbar |
49 |
wird, sollte man C<poll_cb> aufrufen. Für Gtk2 sieht das z.B. so aus: |
50 |
|
51 |
add_watch Glib::IO IO::AIO::poll_fileno, |
52 |
in => sub { IO::AIO::poll_cb; 1 }; |
53 |
|
54 |
Für AnyEvent dagegen etwas komplexer, da es einen Filehandle benötigt: |
55 |
|
56 |
open my $fh, "<&=" . IO::AIO::poll_fileno or die "$!"; |
57 |
my $w = AnyEvent->io (fh => $fh, poll => ’r’, cb => sub { IO::AIO::poll_cb }); |
58 |
|
59 |
=head2 The Works |
60 |
|
61 |
Hat man IO::AIO erst einmal integriert, kann man wild drauf loslegen. Fast |
62 |
alle Operationen, die mit Dateien zu tun haben, werden von IO::AIO |
63 |
unterstützt: |
64 |
|
65 |
aio_sendfile aio_read aio_write aio_open aio_close aio_stat |
66 |
aio_lstat aio_unlink aio_rmdir aio_readdir aio_symlink |
67 |
aio_readlink aio_fsync aio_fdatasync aio_rename aio_link |
68 |
aio_mknod aio_mkdir |
69 |
|
70 |
Allen ist gemeinsam, daß sie ein oder mehrere Parameter verlangen und als |
71 |
letztes einen Callback, der im Fehlerfall oder bei erfolgreichem Abschluß |
72 |
der Operation aufgerufen wird. Anders als die eingebauten Perl-Funktionen |
73 |
geben die meiste aio-Operationen direkt den Status des entsprechenden |
74 |
Syscalls zurück: |
75 |
|
76 |
# Perl |
77 |
stat $path or die ... |
78 |
|
79 |
# IO::AIO |
80 |
aio_stat $path, # vvv and statt or |
81 |
sub { $_[0] and die "..." } |
82 |
|
83 |
=head2 The Features |
84 |
|
85 |
IO::AIO bietet auch eine Reihe von zusammengesetzten Anfragen. |
86 |
|
87 |
So verschieben bzw. kopieren C<aio_move> C<aio_copy> einzelne Dateien. Mit |
88 |
C<aio_load> kann man eine Datei komplett einlesen, C<aio_rmtree> |
89 |
löscht Verzeichnisbäume, C<aio_readahead> liest eine Datei in den |
90 |
OS-Cache (pre-caching) und C<aio_scandir> liefert alle Einträge eines |
91 |
Verzeichnisses in zwei Gruppen sortiert zurück: Verzeichnisse und |
92 |
Sonstige. Der Algorithmus von C<aio_scandir> ist recht komplex aber dafür |
93 |
sehr, sehr schnell. |
94 |
|
95 |
=head2 The Specialties |
96 |
|
97 |
Die meisten der etwas spezielleren Anfragen (sogenannte I<composite |
98 |
requests>) bestehen aus mehreren Einzelanfragen. C<aio_load> z.B. öffnet |
99 |
die Datei und liest dann deren Inhalt mit C<aio_read>. Damit diese |
100 |
Anfragen nach aussen wie eine Anfrage aussehen, kann man Anfragen mit |
101 |
C<aio_group> zusammengruppieren. |
102 |
|
103 |
Die Implementation von C<aio_load> sieht Beispielsweise so aus: |
104 |
|
105 |
sub aio_load($$;$) { |
106 |
aio_block { |
107 |
my ($path, undef, $cb) = @_; |
108 |
my $data = \$_[1]; |
109 |
|
110 |
my $pri = aioreq_pri; |
111 |
my $grp = aio_group $cb; |
112 |
|
113 |
aioreq_pri $pri; |
114 |
add $grp aio_open $path, O_RDONLY, 0, sub { |
115 |
my $fh = shift |
116 |
or return $grp->result (-1); |
117 |
|
118 |
aioreq_pri $pri; |
119 |
add $grp aio_read $fh, 0, (-s $fh), $$data, 0, sub { |
120 |
$grp->result ($_[0]); |
121 |
}; |
122 |
}; |
123 |
|
124 |
$grp |
125 |
} |
126 |
} |
127 |
|
128 |
Zur Erläuterung: C<aio_block> ist eine Art Locking-Mechanismus, der |
129 |
benötigt wird, wenn man asynchone Rückmeldungen wünscht. Das ist nicht |
130 |
Normalbetrieb und darf hier ignoriert werden. |
131 |
|
132 |
Zuest wird daher eine Gruppe erzeugt (C<aioreq_pri> dient dazu, |
133 |
Anfragen untereinander zu prioritisieren). In diese Gruppe wird eine |
134 |
C<aio_open>-Anfrage gesteckt und - falls erfolgreich - gleich noch das |
135 |
C<aio_read>. |
136 |
|
137 |
=head2 Request-Objekte |
138 |
|
139 |
Alle Anfragen liefern (auf Wunsch :) ein Objekt zurück: |
140 |
|
141 |
my $buffer; |
142 |
my $loader = aio_load $path, $buffer, sub { |
143 |
# $buffer voll, hoffentlich |
144 |
}; |
145 |
|
146 |
Mit diesem Objekt kann man jede Operation unter andere, abbrechen, falls |
147 |
diese nicht schon bearbeitet wird: |
148 |
|
149 |
$loader->cancel; # wills nicht mehr haben |
150 |
|
151 |
Dies ist vor allem für interaktive Programme nützlich, die auf |
152 |
Benutzerwunsch eine Operationen starten, diese aber abbrechen, falls der |
153 |
Benutzer nicht mehr daran interessiert ist. |
154 |
|
155 |
=head2 Anwendungsbeispiel |
156 |
|
157 |
IO::AIO wird, unter anderem, von dne folgendne drei Programmen benutzt: |
158 |
|
159 |
=head3 Gtk2::CV |
160 |
|
161 |
Ein interaktives Bildbetrachtungsprogramm. Geschrieben wurde es, um die |
162 |
wichtigsten Features von XV in das 21te Jahrhundert zu retten, bzw. |
163 |
schnell und effizient große Bildverzeichnisse zu durchforsten. Es benutzt |
164 |
IO::AIO extensiv, um z.B. Dateien für den Thumbnailprozess zu lesen (es |
165 |
ist wahrscheinlich einer der schnellsten indexer "wo gibt"), Dateien zu |
166 |
verschieben, zu löschen und vieles mehr. |
167 |
|
168 |
=head3 myhttpd |
169 |
|
170 |
Ist ein single-Prozess-Webserver, der schon vor fast 10 Jahren 1400 |
171 |
Downloader gleichzeitig mit 80MBit/s von nur zwei Festplatten bedienen |
172 |
konnte. Damals noch mit Linux::AIO, benutzt er inzwischen IO::AIO für |
173 |
fats alle Dateiperationen, insbesondere natürlich für das Serven großer |
174 |
Dateien. |
175 |
|
176 |
=head3 Crossfire+ |
177 |
|
178 |
Crossfire+ ist ein grafisches Multiplayer-Online-RPG (bzw. eines der |
179 |
ältesten). Perl und IO::AIO haben ihm vor kurzer Zeit beigebracht, |
180 |
Karten/Levels und andere Spieldaten im Hintergrund zu Laden und zu |
181 |
Speichern, so daß auch bei vielen Spielern und einem laufenden Backup im |
182 |
Hintergrund keine lästigen Pausen mehr entstehen. |
183 |
|
184 |
=head2 Coro::AIO - IO::AIO linearisiert |
185 |
|
186 |
Mit Coro::AIO kann man alle IO::AIO-Operationen wieder "linear" |
187 |
benutzen. Crossfire+ z.B. benutzt IO::AIO meistens nicht direkt, sondern |
188 |
über Coro::AIO. Eine Map oder andere Daten werden z.B. so geschrieben: |
189 |
|
190 |
if (my $fh = aio_open "$filename~", O_WRONLY | O_CREAT, 0600) { |
191 |
chmod SAVE_MODE, $fh; |
192 |
aio_write $fh, 0, (length $$rdata), $$rdata, 0; |
193 |
aio_fsync $fh; |
194 |
close $fh; |
195 |
aio_rename "$filename~", $filename; |
196 |
} else { |
197 |
warn "FATAL: $filename~: $!\n"; |
198 |
} |
199 |
|
200 |
Den Callback läßt man weg, stattdessen werden die Ergebnisse an den |
201 |
Aufrufer zurückgeliefert. Die aktuelle Coroutine wird zwar blockiert, |
202 |
aber andere Coroutinen (z.B. der Server selbst, der Map-Updates an die |
203 |
Spieler liefert) läuft aber ungehindert weiter. |
204 |
|
205 |
=head2 Autor |
206 |
|
207 |
Marc Lehmann <pcg@goof.com> |