Pipes in Python
Pipe
Die Pipe
(engl. für Rohr, Röhre)
bezeichnet einen gepufferten
uni- oder bidirektionalen
Datenstrom zwischen zwei
Prozessen nach dem
"FIFO" (First In - First Out)-
Prinzip.
Das bedeutet, dass die
Ausgabe eines Prozesses als Eingabe für einen weiteren verwendet wird.
Pipes wurden 1973 in Unix eingeführt.
Generell unterscheidet man zwei Sorten von Pipes:
- anonyme (anonymous pipes)
und - benamte (named pipes)
Bier-Pipe in Python
"99 Bottles of Beer" ist ein bekanntes und beliebtes Lied in den USA und Kanada. Es wird häufig bei gemeinsam unternommenen Ausflügen und Feiern gesungen. Obwohl der Song aus 100 verschiedenen Strophen besteht, stellt er keine besonderen Anforderungen an die Gedächtnisleistung der Sängerinnen und Sänger:Ninety-nine bottles of beer on the wall, Ninety-nine bottles of beer. Take one down, pass it around, Ninety-eight bottles of beer on the wall.
Dann geht es mit 98 Flaschen entsprechend weiter bis alle Flaschen "aufgebraucht" sind. Was tun, wenn kein Bier mehr da ist? Klar, neues kaufen gehen:
No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, Ninety-nine bottles of beer on the wall.
Dieses Lied hat auch in der Programmierung eine besondere Bedeutung erlangt. Dieser Song wurde mittlerweile in allen bekannten Programmiersprachen implementiert.
Wir programmieren die Aleph-Null-Variante, d.h. mit der "no more bottles"-Strophe, mit Forking und Pipe:
import os def child(pipeout): bottles = 99 while True: bob = "bottles of beer" otw = "on the wall" take1 = "Take one down and pass it around" store = "Go to the store and buy some more" if bottles > 0: values = (bottles, bob, otw, bottles, bob, take1, bottles - 1,bob,otw) verse = "%2d %s %s,\n%2d %s.\n%s,\n%2d %s %s." % values os.write(pipeout, verse) bottles -= 1 else: bottles = 99 values = (bob, otw, bob, store, bottles, bob,otw) verse = "No more %s %s,\nno more %s.\n%s,\n%2d %s %s." % values os.write(pipeout, verse) def parent(): pipein, pipeout = os.pipe() if os.fork() == 0: child(pipeout) else: counter = 1 while True: if counter % 100: verse = os.read(pipein, 117) else: verse = os.read(pipein, 128) print 'verse %d\n%s\n' % (counter, verse) counter += 1 parent()Das Problem im obigen Beispiel besteht darin, dass wir genau wissen müssen, wieviele Bytes wir jeweils vom Child-Prozess über die Pipe erwarten. Für die ersten 99 Strophen sind es jeweils 117 Bytes (
verse = os.read(pipein, 117)
) und für die Strophe ohne
Flaschen 128 Bytes (verse = os.read(pipein, 128)
).
In der folgenden Implementierung lesen wir jeweils ganze Zeilen aus der Pipe des Kind-Prozesses:
import os def child(pipeout): bottles = 99 while True: bob = "bottles of beer" otw = "on the wall" take1 = "Take one down and pass it around" store = "Go to the store and buy some more" if bottles > 0: values = (bottles, bob, otw, bottles, bob, take1, bottles - 1,bob,otw) verse = "%2d %s %s,\n%2d %s.\n%s,\n%2d %s %s.\n" % values os.write(pipeout, verse) bottles -= 1 else: bottles = 99 values = (bob, otw, bob, store, bottles, bob,otw) verse = "No more %s %s,\nno more %s.\n%s,\n%2d %s %s.\n" % values os.write(pipeout, verse) def parent(): pipein, pipeout = os.pipe() if os.fork() == 0: os.close(pipein) child(pipeout) else: os.close(pipeout) counter = 1 pipein = os.fdopen(pipein) while True: print 'verse %d' % (counter) for i in range(4): verse = pipein.readline()[:-1] print '%s' % (verse) counter += 1 print parent()
Bidirektionale Pipes
Wir wollen die Implementierung von bidriektionalen Pipes anhand eines Spieles demonstrieren. Dabei handelt es sich um ein einfaches Zahlenratespiels, wie es von kleinen Kindern gerne gespielt wird. Dieses Spiel hatten wir bereits im Kapitel über Schleifen in unserem Python-Kurs verwendet.Das folgende Diagram veranschaulicht sowohl die Regeln des Spiels, als auch die Implementierung in unserem Python-Skript. Ein Spieler, in unserem Fall der Eltern-Prozess mit der Funktion "deviser", denkt sich eine Zahl im Bereich 0 bis 100. Der andere Spieler, bei uns der Kindprozess mit der Funktion "guesser", versucht die Zahl zu erraten. Der Elternprozess gibt eine 0 zurück, wenn die Zahl erraten wurde, eine 1, falls die geratene Zahl größer als die zu erratende Zahl ist und ein -1, falls sie kleiner als die zu erratende Zahl ist. Sowohl der Elternprozess als auch der Kindprozess protokollieren die Zwischenergebnisse, d.h. die Zahlen, die geraten werden. deviser schreibt in deviser.log und guesser schreibt in guesser.log
Im folgenden haben wir eine Implementierung des gesamten Skriptes angegeben:
import os, sys, random def deviser(max): fh = open("devisor.log","w") to_be_guessed = int(max * random.random()) + 1 guess = 0 while guess != to_be_guessed: guess = int(raw_input()) fh.write(str(guess) + " ") if guess > 0: if guess > to_be_guessed: print 1 elif guess < to_be_guessed: print -1 else: print 0 sys.stdout.flush() else: break fh.close() def guesser(max): fh = open("guesser.log","w") bottom = 0 top = max fuzzy = 10 res = 1 while res != 0: guess = (bottom + top) / 2 print guess sys.stdout.flush() fh.write(str(guess) + " ") res = int(raw_input()) if res == -1: # number is higher bottom = guess elif res == 1: top = guess elif res == 0: message = "Wanted number is %d" % guess fh.write(message) else: # this case shouldn't occur print "input not correct" fh.write("Something's wrong") n = 100 stdin = sys.stdin.fileno() # usually 0 stdout = sys.stdout.fileno() # usually 1 parentStdin, childStdout = os.pipe() childStdin, parentStdout = os.pipe() pid = os.fork() if pid: # parent process os.close(childStdout) os.close(childStdin) os.dup2(parentStdin, stdin) os.dup2(parentStdout, stdout) deviser(n) else: # child process os.close(parentStdin) os.close(parentStdout) os.dup2(childStdin, stdin) os.dup2(childStdout, stdout) guesser(n)
Benamte Pipes, Fifos
Unter Linux ebenso wie unter Unix ist es möglich Pipes anzulegen, die als Dateien eingerichtet sind.Diese Pipes werden als benamte Pipes (englisch: named Pipes) oder manchmal auch als Fifos (First In First Out) bezeichnet.
Ein Prozess liest und schreibt von einer solchen Pipe wie bei einer normalen Datei. Oft schreiben mehrere Prozesse in eine Pipe und nur ein Prozess liest daraus.
Das folgende Beispiel illustriert den Fall, dass ein Prozess in die Pipe schreibt, der Kindprozess, und ein anderer Prozess, der Elternprozess, aus der Pipe liest.
import os, time, sys pipe_name = 'pipe_test' def child( ): pipeout = os.open(pipe_name, os.O_WRONLY) counter = 0 while True: time.sleep(1) os.write(pipeout, 'Number %03d\n' % counter) counter = (counter+1) % 5 def parent( ): pipein = open(pipe_name, 'r') while True: line = pipein.readline()[:-1] print 'Parent %d got "%s" at %s' % (os.getpid(), line, time.time( )) if not os.path.exists(pipe_name): os.mkfifo(pipe_name) pid = os.fork() if pid != 0: parent() else: child()