| 1 |
root |
1.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> |