# תהליכי משנה ו־co-processes משימת ה־IPC הנפוצה ביותר היא הפשוטה ביותר: הרצת פקודה חיצונית ותקשורת איתה. עמוד זה מכסה את כל הטווח, מ־pipe open חד־כיווני ועד co-process דו־כיווני - ילד שאליו גם כותבים וגם ממנו קוראים. מנגנון ה־pipe open החד־כיווני (`"-|"` ו־`"|-"`) מתואר במלואו בעמוד [Pipes](../io-streams/pipes.md). עמוד זה ממשיך מהנקודה שבה ההוא עוצר: כאשר כיוון אחד אינו מספיק, וכאשר נדרשים [`pipe`](../../p5/core/perlfunc/pipe.md) ו־[`fork`](../../p5/core/perlfunc/fork.md) גולמיים משלך. ## כיוון אחד: ה־pipe open כדי לקרוא את הפלט של פקודה, או כדי להזין לפקודה את הקלט שלה, pipe open הוא כל מה שנדרש: ```perl open my $fh, "-|", "sort", "-u", "names.txt" or die "pipe: $!"; while (my $line = <$fh>) { ... } close $fh; ``` זהו כל הסיפור לעבודה חד־כיוונית, והוא מסופר בעמוד [Pipes](../io-streams/pipes.md) - כולל ההבדל בין צורת הרשימה לצורת המחרוזת, מדוע צורת הרשימה עוקפת את ה־shell, וכיצד לפענח את סטטוס היציאה של הילד. התחל שם אם כיוון אחד מספיק. ## שני הכיוונים: `IPC::Open2` כאשר נדרש לכתוב לפקודה *וגם* לקרוא את תשובתה - מסנן שמפעילים באופן אינטראקטיבי, co-process - pipe אחד אינו מספיק. `IPC::Open2` פותח שניים: אחד אל תוך ה־`stdin` של הילד, ואחד החוצה מה־`stdout` שלו. ```perl 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` מחזיר את ה־process id של הילד. סדר הארגומנטים הוא המלכודת שכדאי לזכור: **מטפל הקריאה תחילה, מטפל הכתיבה שני**, ולאחר מכן הפקודה. `open2(OUT, IN, ...)` - המטפל שממנו *קוראים* מופיע לפני המטפל שאליו *כותבים*. ### ה־deadlock שיש לתכנן סביבו co-process עלול להיכנס ל־deadlock, ו־`IPC::Open2` אינו עושה דבר כדי למנוע זאת. צורת המלכודת: - אתה כותב בלוק גדול אל הילד. - הילד קורא חלק, מייצר פלט, והפלט הזה ממלא את ה־pipe חזרה אליך. - הילד נחסם בכתיבה ל־pipe מלא; אתה נחסם בכתיבה לילד שהפסיק לקרוא. אף אחד אינו מתקדם. המתכון שלמעלה עוקף זאת על־ידי סגירת `$to_child` לפני הקריאה - שליחת כל הקלט, לאחר מכן איתות סוף־קובץ, ולאחר מכן ניקוז התשובה. זה עובד כאשר הקלט נכנס בנוחות לבופר ה־pipe. עבור streaming בלתי חסום בשני הכיוונים בו־זמנית, נדרשים קלט/פלט לא־חוסם או לולאת `select`; צורת שני ה־pipe לבדה אינה מספיקה. ## לכידת שגיאת התקן גם כן: `IPC::Open3` `IPC::Open2` מחבר אותך אל ה־`stdin` וה־`stdout`. כאשר נדרש גם ה־`stderr` של הילד על מטפל משלו, `IPC::Open3` מוסיף שלישי: ```perl 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`](../../p5/core/perlfunc/pipe.md) גולמי מספק זוג קורא/כותב מחובר לפני ה־fork; לאחר [`fork`](../../p5/core/perlfunc/fork.md) כל צד שומר את הקצה שהוא זקוק לו וסוגר את האחר: ```perl 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 sockets](sockets-tcp.md): **כל צד סוגר את הקצה שאינו משתמש בו.** אם ההורה משאיר את קצה הכתיבה פתוח, ה־`close` של הילד לעולם אינו מייצר סוף־קובץ בקצה הקריאה, והקריאה של ההורה נחסמת לנצח. `fork()` מפורש יוצר תהליך נפרד של מערכת ההפעלה; ה־auto-parallelism של PetaPerl הוא מאגר threads בתוך־התהליך. ילד שנוצר ב־fork מריץ את הצמצומים המספריים האוטו־מקביליים שלו כרגיל בתוך עצמו, וההקבלה של ההורה ושל הילד אינן מפריעות זו לזו - אלה תהליכים עצמאיים עם מאגרי threads עצמאיים. עבור הרצת עבודה על פני ליבות CPU *בתוך* תהליך אחד במקום על פני תהליכים נפרדים, ראה את מדריך [Concurrent Execution](../../guide/concurrent-execution/index.md). ## קישורי הצלבה לעיון - [`open`](../../p5/core/perlfunc/open.md) - ה־pipe open החד־כיווני (`"-|"`, `"|-"`) - [`pipe`](../../p5/core/perlfunc/pipe.md) - זוג מטפלים מחובר גולמי - [`fork`](../../p5/core/perlfunc/fork.md) - פיצול לשני תהליכים - [`waitpid`](../../p5/core/perlfunc/waitpid.md) - איסוף הילד ושחזור הסטטוס שלו - [`exec`](../../p5/core/perlfunc/exec.md) - החלפת תמונת הילד בפקודה ## הבא עבור שתי תוכניות שאינן קשורות שנפגשות בנתיב במערכת הקבצים ולא דרך הורה משותף, ראה [FIFOs](fifos.md).