Υποδιεργασίες και συνεργαζόμενες διεργασίες#
Η πιο συνηθισμένη εργασία IPC είναι η απλούστερη: εκτέλεση μιας εξωτερικής εντολής και επικοινωνία μαζί της. Αυτή η σελίδα καλύπτει το φάσμα, από ένα μονόδρομο άνοιγμα σωλήνα μέχρι μια αμφίδρομη συνεργαζόμενη διεργασία - ένα παιδί στο οποίο τόσο γράφετε όσο και διαβάζετε από αυτό.
Οι μηχανισμοί ενός μονόδρομου ανοίγματος σωλήνα ("-|" και "|-") καλύπτονται πλήρως στη σελίδα Σωλήνες. Αυτή η σελίδα συνεχίζει εκεί όπου σταματά εκείνη: όταν μία κατεύθυνση δεν αρκεί, και όταν χρειάζεστε ένα δικό σας χαμηλού επιπέδου pipe και fork.
Μία κατεύθυνση: το άνοιγμα σωλήνα#
Για να διαβάσετε την έξοδο μιας εντολής, ή για να τροφοδοτήσετε μια εντολή με την είσοδό της, ένα άνοιγμα σωλήνα είναι το μόνο που χρειάζεστε:
open my $fh, "-|", "sort", "-u", "names.txt" or die "pipe: $!";
while (my $line = <$fh>) { ... }
close $fh;
Αυτή είναι όλη η ιστορία για τη μονόδρομη εργασία, και αφηγείται στη σελίδα Σωλήνες - συμπεριλαμβανομένης της μορφής λίστας έναντι συμβολοσειράς, του γιατί η μορφή λίστας παρακάμπτει το κέλυφος, και του πώς να αποκωδικοποιήσετε την κατάσταση εξόδου του παιδιού. Ξεκινήστε από εκεί αν μία κατεύθυνση αρκεί.
Και οι δύο κατευθύνσεις: IPC::Open2#
Όταν χρειάζεται να γράψετε σε μια εντολή και να διαβάσετε την απάντησή της - ένα φίλτρο που οδηγείτε διαδραστικά, μια συνεργαζόμενη διεργασία - ένας σωλήνας δεν αρκεί. Το IPC::Open2 ανοίγει δύο: έναν προς το stdin του παιδιού, έναν από το stdout του.
use IPC::Open2;
my $pid = open2(my $from_child, my $to_child, "tr", "a-z", "A-Z");
print $to_child "hello\n";
close $to_child; # signal end-of-input to the child
my $reply = <$from_child>;
print "got: $reply"; # got: HELLO
waitpid($pid, 0);
Η open2 επιστρέφει το αναγνωριστικό διεργασίας του παιδιού. Η σειρά των ορισμάτων είναι η παγίδα που αξίζει να απομνημονεύσετε: πρώτα ο περιγραφέας ανάγνωσης, δεύτερος ο περιγραφέας εγγραφής, μετά η εντολή. open2(OUT, IN, ...) - ο περιγραφέας από τον οποίο διαβάζετε προηγείται του περιγραφέα στον οποίο γράφετε.
Το αδιέξοδο γύρω από το οποίο πρέπει να σχεδιάσετε#
Μια συνεργαζόμενη διεργασία μπορεί να φτάσει σε αδιέξοδο, και το IPC::Open2 δεν κάνει τίποτα για να το αποτρέψει. Το σχήμα της παγίδας:
Γράφετε ένα μεγάλο μπλοκ στο παιδί.
Το παιδί διαβάζει ένα μέρος, παράγει έξοδο, και αυτή η έξοδος γεμίζει τον σωλήνα πίσω προς εσάς.
Το παιδί μπλοκάρει γράφοντας σε έναν γεμάτο σωλήνα· εσείς μπλοκάρετε γράφοντας σε ένα παιδί που έχει σταματήσει να διαβάζει. Κανείς δεν προχωρά.
Η παραπάνω συνταγή το παρακάμπτει κλείνοντας το $to_child πριν από την ανάγνωση - στέλνοντας ολόκληρη την είσοδο, μετά σηματοδοτώντας το τέλος αρχείου, μετά αποστραγγίζοντας την απάντηση. Αυτό λειτουργεί όταν η είσοδος χωρά άνετα στον ενταμιευτή του σωλήνα. Για απεριόριστη ροή και στις δύο κατευθύνσεις ταυτόχρονα, χρειάζεστε I/O χωρίς μπλοκάρισμα ή έναν βρόχο select· το σχήμα των δύο σωλήνων από μόνο του δεν αρκεί.
Σύλληψη και του τυπικού σφάλματος: IPC::Open3#
Το IPC::Open2 σας συνδέει με το stdin και το stdout. Όταν χρειάζεστε επίσης το stderr του παιδιού σε δικό του περιγραφέα, το IPC::Open3 προσθέτει έναν τρίτο:
use IPC::Open3;
use Symbol qw(gensym);
my $err = gensym; # a fresh anonymous handle for stderr
my $pid = open3(my $to_child, my $from_child, $err, "sort");
print $to_child "banana\napple\ncherry\n";
close $to_child;
my @sorted = <$from_child>; # ("apple\n", "banana\n", "cherry\n")
print @sorted; # apple / banana / cherry, one per line
waitpid($pid, 0);
Δύο διαφορές από την open2 που πρέπει να σημειώσετε:
Η σειρά των ορισμάτων αντιστρέφεται. Η
open3παίρνει πρώτα τον περιγραφέα εγγραφής, μετά τον αναγνώστη, μετά τον περιγραφέα σφάλματος:open3(IN, OUT, ERR, ...). Αυτό είναι το αντίθετο της σειράς αναγνώστη-πρώτα τηςopen2- μια μακροχρόνια ασυνέπεια που δαγκώνει τον καθένα μία φορά.Ο περιγραφέας σφάλματος χρειάζεται
gensym. Περάστε έναν φρέσκο ανώνυμο περιγραφέα από τηνSymbol::gensymγια τοstderr· ένα bareword ή μια απροσδιόριστη λεξιλογική μεταβλητή δεν θα το συλλέξει καθαρά.
Φτιάχνοντας το δικό σας: pipe και fork#
Όταν η εντολή δεν είναι ένα εξωτερικό πρόγραμμα αλλά κώδικας Perl που θέλετε να εκτελέσετε σε μια ξεχωριστή διεργασία, χτίστε το κανάλι μόνοι σας. Ένα χαμηλού επιπέδου pipe δίνει ένα συνδεδεμένο ζεύγος αναγνώστη/εγγραφέα πριν από το fork· μετά το fork κάθε πλευρά κρατά το άκρο που χρειάζεται και κλείνει το άλλο:
pipe(my $reader, my $writer) or die "pipe: $!";
my $kid = fork() // die "fork: $!";
if ($kid == 0) {
# Child writes, then exits.
close $reader; # child has no use for the read end
$writer->autoflush(1);
print $writer "down-the-pipe\n";
close $writer;
exit 0;
}
# Parent reads.
close $writer; # parent has no use for the write end
my $line = <$reader>;
print "parent read: $line"; # parent read: down-the-pipe
close $reader;
waitpid($kid, 0);
Η πειθαρχία είναι ίδια με του διακομιστή που κάνει fork στη σελίδα Υποδοχές TCP: κάθε πλευρά κλείνει το άκρο που δεν χρησιμοποιεί. Αν ο γονέας κρατήσει το άκρο εγγραφής ανοιχτό, το close του παιδιού δεν παράγει ποτέ τέλος αρχείου στο άκρο ανάγνωσης, και η ανάγνωση του γονέα μπλοκάρει για πάντα.
Το ρητό fork() δημιουργεί μια ξεχωριστή διεργασία λειτουργικού συστήματος· η αυτόματη παραλληλοποίηση της PetaPerl είναι μια δεξαμενή νημάτων εντός της διεργασίας. Ένα παιδί που δημιουργήθηκε με fork εκτελεί κανονικά τις δικές του αυτόματα παραλληλοποιημένες αριθμητικές αναγωγές μέσα του, και ο παραλληλισμός του γονέα και του παιδιού δεν παρεμβαίνουν μεταξύ τους - είναι ανεξάρτητες διεργασίες με ανεξάρτητες δεξαμενές νημάτων. Για την εκτέλεση εργασίας σε πολλούς πυρήνες CPU μέσα σε μία διεργασία αντί σε ξεχωριστές διεργασίες, δείτε τον οδηγό Ταυτόχρονη εκτέλεση.
Διασταυρούμενοι σύνδεσμοι αναφοράς#
Επόμενο#
Για δύο μη σχετιζόμενα προγράμματα που συναντιούνται σε μια διαδρομή του συστήματος αρχείων αντί μέσω κοινού γονέα, δείτε FIFO.