תהליכים

wait#

חסימה עד שתהליך־ילד כלשהו יוצא ואיסופו.

wait הוא ממשק הקצה הפשוט ביותר לקריאת המערכת הבסיסית wait(2). הוא משעה את התהליך הנוכחי עד שאחד מילדיו מסתיים, אוסף את סטטוס היציאה של אותו ילד לתוך $? ו־${^CHILD_ERROR_NATIVE}, ומחזיר את ה־PID של הילד שנאסף. אם אין ילדים להמתין להם, הוא מחזיר -1 מיד. הוא שקול בדיוק ל־waitpid(-1, 0).

תקציר#

my $pid = wait;

מה מקבלים בחזרה#

ה־PID של הילד שנאסף (מספר שלם חיובי), או -1 אם אין ילדים לא־אסופים שנותרו להמתין להם. סטטוס הילד עובר לתוך $?; מילת הסטטוס הגולמית הספציפית למערכת ההפעלה עוברת לתוך ${^CHILD_ERROR_NATIVE}.

לפענוח $? אחרי wait מוצלח:

my $exit   = $? >> 8;       # normal exit code (0..255)
my $signal = $? & 0x7f;     # termination signal, if any
my $core   = $? & 0x80;     # core-dump flag

החזרה של -1 לא תמיד משמעה ״מעולם לא היו ילדים״ - היא יכולה גם להיות ״משהו כבר אסף אותם בשבילי״ (ראו למטה).

מצב גלובלי שהוא נוגע בו#

  • $? - מוגדר לסטטוס ההמתנה של הילד שנאסף ברוחב 16 ביט (בית עליון: קוד היציאה, בית תחתון: אות הסיום ודגל core). ניתן לקריאה גם כ־$CHILD_ERROR תחת use English.

  • ${^CHILD_ERROR_NATIVE} - מילת הסטטוס הגולמית של הפלטפורמה, עבור קוראים שרוצים להשתמש במאקרואים של POSIX (WIFEXITED, WEXITSTATUS, WIFSIGNALED, WTERMSIG) מ־POSIX.

  • %SIG - אם $SIG{CHLD} מוגדר ל־'IGNORE', או שמטפל בו קורא ל־wait בעצמו, תוצאת קריאה זו משתנה. ראו מקרי קצה.

דוגמאות#

בצעו fork לעובד, עשו משהו, ואז אספו אותו:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    # child
    exec "/usr/bin/true" or die "exec: $!";
}
my $reaped = wait;              # blocks until the child exits
die "unexpected pid" if $reaped != $pid;
printf "child %d exited %d\n", $reaped, $? >> 8;

איסוף כל ילד תלוי ועומד בלולאה. wait מחזיר -1 ברגע שלתהליך לא נותרו ילדים:

while ((my $kid = wait) > 0) {
    warn "reaped $kid, status " . ($? >> 8) . "\n";
}

הבחנה בין יציאה רגילה לסיום באות:

my $pid = wait;
if ($pid > 0) {
    if    ($? == 0)       { say "clean exit" }
    elsif ($? & 0x7f)     { say "killed by signal " . ($? & 0x7f) }
    else                  { say "exit " . ($? >> 8) }
}

פענחו את מילת הסטטוס המקומית עם מאקרואים של POSIX, מה שנייד יותר מהזזת ביטים ידנית:

use POSIX ":sys_wait_h";
my $pid = wait;
if ($pid > 0 && WIFEXITED(${^CHILD_ERROR_NATIVE})) {
    say "exit code: ", WEXITSTATUS(${^CHILD_ERROR_NATIVE});
}

איסוף לא־חוסם של כל zombie ממתין - השתמשו ב־waitpid עם WNOHANG, לא ב־wait:

use POSIX ":sys_wait_h";
1 while waitpid(-1, WNOHANG) > 0;

מקרי קצה#

  • אין ילדים להמתין להם: מחזיר -1 מיד. wait אינו חוסם במקרה זה - הוא חוסם רק כאשר לפחות ילד אחד עדיין חי ולא־אסוף.

  • $SIG{CHLD} = 'IGNORE': במערכות POSIX זה מבקש מהקרנל לבצע איסוף־אוטומטי של ילדים. תחת הגדרה זו, wait כמעט תמיד מחזיר -1 משום שהילדים נעלמו לפני שקוד המשתמש יכול לקרוא לו. $? אינו מוגדר. אם רוצים גם ילדים בסגנון שגר־ושכח וגם דיווח סטטוס, השתמשו במטפל אמיתי, לא ב־'IGNORE'.

  • מטפל $SIG{CHLD} מותאם אישית: אם המטפל קורא ל־wait (או waitpid) בעצמו, wait עוקב בזרימה הראשית ימצא שדבר לא נותר ויחזיר -1. החליטו מראש איזה מסלול קוד מבצע את האיסוף.

  • אינטראקציה עם qx// ו־system: שני אלה מבצעים פנימית fork, exec, וממתינים על הילד שלהם. מטפל $SIG{CHLD} שקורא ל־wait בעיוורון יכול בטעות לאסוף את הילד הפנימי ההוא מתחת ל־qx// או system, ולהשאיר אותם בלתי־מסוגלים לאסוף את הסטטוס ובדרך כלל מחזירים -1 כאשר $! מוגדר ל־ECHILD. מטפלים צריכים ללולל עם waitpid(-1, WNOHANG) ולפעול רק על PID־ים שהם מזהים.

  • $? נכתב רק באיסוף מוצלח. אחרי החזרה של -1, $? שומר על מה שהיה לו לפני הקריאה. אל תבחנו אותו אלא אם wait החזיר PID חיובי.

  • הופרע על־ידי אות: אם אות נמסר בעוד wait חסום והמטפל חוזר כרגיל, Perl מאתחל מחדש את ההמתנה - לא רואים EINTR ברמת Perl. אם המטפל מבצע die או exit, הבקרה עוזבת את wait באופן הרגיל לאותו מנגנון.

  • בנייות מרובות־thread: wait ממתין לילדים של התהליך הנוכחי, לא לילדים של thread ספציפי. בתוכנית מרובת־thread thread האיסוף הוא זה שקרא ל־wait ראשון.

  • ערך ההחזרה הוא PID, לא בוליאני: if (wait) { ... } הוא אמת גם לאיסופים מוצלחים וגם ל־-1. תמיד השוו ל־0 או בדקו > 0 במפורש, כמו בצורת הלולאה לעיל.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • waitpid - המתנה לילד ספציפי, או poll לא־חוסם עם WNOHANG; wait הוא הצורה waitpid(-1, 0)

  • fork - יצירת הילדים ש־wait אוסף

  • exec - מה שילד forked קורא בדרך כלל כדי להפוך לתוכנית אחרת

  • system - fork + exec + wait בקריאה אחת; משתמש ב־wait פנימי משלו ומגדיר את $? עבורכם

  • kill - שליחת אות לילד שלא רוצים יותר להמתין לו

  • $? - היכן שסטטוס הילד שנאסף נוחת, מקודד כבית עליון של קוד יציאה ובית תחתון של אות/core