קלט/פלט

flock#

הצבת נעילה ייעוצית על קובץ פתוח.

flock הוא הפרימיטיב הנייד של Perl לנעילת קבצים. הוא קורא ל־flock(2), fcntl(2), או lockf(3) התשתיתי - לפי מה שהפלטפורמה מספקת - על FILEHANDLE וחוסם כברירת מחדל עד שהנעילה המבוקשת ניתנת. נעילות מכסות את הקובץ כולו, לעולם לא טווח בתים, והן ייעוציות: הן מתאמות רק בין תהליכים שגם קוראים ל־flock על אותו קובץ. קוד שמתעלם מהנעילה יכול עדיין לקרוא או לשנות את התוכן.

תקציר#

flock FILEHANDLE, OPERATION

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

אמת בהצלחה, שקר בכישלון (עם $! מוגדר). תמיד יש לבדוק את ערך ההחזרה - עם LOCK_NB כישלון הוא האות הרגיל ״מישהו אחר מחזיק בה״, לא שגיאה:

flock($fh, LOCK_EX | LOCK_NB)
    or die "already locked: $!";

בפלטפורמה שאינה מממשת אף אחד מ־flock(2), נעילת fcntl(2), או lockf(3), flock מעלה שגיאה קטלנית במקום להחזיר שקר. ב־Linux (הפלטפורמה היחידה הנתמכת של pperl) זה לעולם לא קורה.

ערכי OPERATION#

OPERATION הוא אחד מסוגי הבקשה השוללים זה את זה, אופציונלית עם OR ביטי לדגל הלא־חוסם. הערכים המספריים המסורתיים הם 1, 2, 4, 8 אך קוד נייד מייבא את השמות הסימבוליים מ־Fcntl, בנפרד או כקבוצה באמצעות התג :flock:

use Fcntl qw(:flock);
  • LOCK_SH - נעילה משותפת. תהליכים מרובים רשאים להחזיק נעילה משותפת על אותו קובץ בו־זמנית. משמש לקוראים.

  • LOCK_EX - נעילה ייחודית. רק תהליך אחד רשאי להחזיק בה; אף תהליך אחר אינו רשאי להחזיק כל נעילה על הקובץ באותו זמן. משמש לכותבים.

  • LOCK_UN - שחרור כל נעילה שמטפל הקובץ הזה מחזיק כעת.

  • LOCK_NB - OR ביטי עם LOCK_SH או LOCK_EX כדי להפוך את הקריאה ללא־חוסמת; flock מחזיר שקר מיד אם הנעילה אינה ניתנת, במקום להמתין.

קריאת flock עם מצב חדש מחליפה אטומית נעילה קיימת על אותו מטפל קובץ (לדוגמה, שדרוג LOCK_SH ל־LOCK_EX).

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

  • קובע את $! בכישלון.

  • פולט את מאגר הפלט של FILEHANDLE לפני נעילה או שחרור נעילה, כך שנתוני האזור הנעול מגיעים למערכת ההפעלה לפני שתהליך אחר יכול לראות את שינוי מצב הנעילה.

דוגמאות#

נעילה ייחודית לכותב, חוסמת עד שתינתן:

use Fcntl qw(:flock);
open my $fh, ">>", "counter.log" or die $!;
flock($fh, LOCK_EX)               or die "lock failed: $!";
print $fh "tick\n";
flock($fh, LOCK_UN)               or die "unlock failed: $!";
close $fh;

ניסיון לא־חוסם - דילוג על העבודה אם תהליך אחר כבר רץ:

use Fcntl qw(:flock);
open my $fh, ">", "/var/run/myjob.lock" or die $!;
flock($fh, LOCK_EX | LOCK_NB)
    or do { warn "another instance running\n"; exit 0 };
# ... work ...
# lock auto-released when $fh is closed / program exits

מצרף mailbox קלאסי. שימו לב ל־seek ל־SEEK_END לאחר הנעילה - במערכות ישנות מאוד מצב הצירוף >> אינו אטומי, ולכן יש לבסס מחדש את עמדת הכתיבה לאחר החזקת הנעילה:

use Fcntl qw(:flock SEEK_END);

sub lock_mbox   { flock($_[0], LOCK_EX) or die "lock: $!" }
sub unlock_mbox { flock($_[0], LOCK_UN) or die "unlock: $!" }

open my $mbox, ">>", "/var/mail/$ENV{USER}" or die $!;
lock_mbox($mbox);
seek($mbox, 0, SEEK_END) or die "seek: $!";
print $mbox $msg, "\n\n";
unlock_mbox($mbox);
close $mbox;

נעילת קריאה משותפת בעת טעינת קובץ תצורה, המאפשרת לקוראים אחרים להמשיך במקביל:

use Fcntl qw(:flock);
open my $fh, "<", "config.dat" or die $!;
flock($fh, LOCK_SH)             or die "lock: $!";
my @lines = <$fh>;
close $fh;                      # releases the lock

שדרוג נעילה משותפת לייחודית - ה־flock השני מחליף מצבים אטומית:

flock($fh, LOCK_SH) or die $!;
# ... inspect contents ...
flock($fh, LOCK_EX) or die $!;  # now we intend to write

מקרי קצה#

  • נעילות הן לכל־מטפל קובץ, לא לכל־קובץ. שתי פתיחות עצמאיות של אותו נתיב, לכל אחת חריץ נעילה משלה. זו הסיבה שנעילת הקובץ שלך פעמיים מאותו תהליך דרך מטפלים שונים יכולה ליצור deadlock או להתנהג באופן מפתיע במערכות מבוססות־fcntl(2).

  • סגירת מטפל הקובץ משחררת את הנעילה. אין צורך בשלב ”שחרור“ נפרד ביציאה מהתוכנית; LOCK_UN שימושי רק כאשר רוצים להשאיר את המטפל פתוח לאחר מכן.

  • כישלון LOCK_NB אינו croak. הוא מחזיר שקר וקובע את $! ל־EWOULDBLOCK. יש להתייחס אליו כתוצאה צפויה, לא כשגיאה.

  • ייעוצי בלבד. תהליך שלעולם אינו קורא ל־flock אינו רואה נעילות. יש להשתמש ב־flock כפרוטוקול שיתוף פעולה בין התוכניות שלך, לא כמנגנון אבטחה.

  • קבצים שלמים בלבד. אין נעילת טווח־בתים דרך flock. עבור נעילה ברמת הרשומה יש להשתמש ב־fcntl עם F_SETLK / F_SETLKW.

  • NFS ומערכות קבצים רשתיות אחרות. מימושי NFS ישנים אחדים אינם יכולים לכבד flock בין מארחים; הקריאה עלולה להיהפך בשקט ל־no-op או להחזיר הצלחה מבלי לספק הוצאה הדדית. יש להשתמש בנעילת fcntl אם תיאום רשת חשוב.

  • הורשה ב־fork. כאשר הפלטפורמה משתמשת ב־flock(2) אמיתי (כפי ש־Linux עושה), נעילות עוברות בירושה דרך fork; הילד חולק אותן עם ההורה. בפלטפורמות שמדמות fcntl(2) הנעילות אינן שורדות את ה־fork, מה שמקשה משמעותית על כתיבת שרתי נעילה.

  • פליטת מאגר. Perl פולט את מאגר הפלט של FILEHANDLE בכל קריאת flock. קוד המשלב print ממוואגר עם נעילה אינו זקוק לפליטה מפורשת לפני גבול הנעילה.

  • אינטראקציה עם פתיחות בסגנון dup. פתיחה בצורת open(my $A, ">>&=", $B) חולקת את ה־file descriptor התשתיתי עם $B, כך ש־flock($A) ו־flock($B) פועלים על אותו חריץ נעילה. dup של >>& נותן לכל מטפל את ה־descriptor שלו ואת חריץ הנעילה שלו.

הבדלים מהמקור#

תאימות מלאה עם Perl 5.42 המקורי. pperl רץ על Linux בלבד, שבו flock(2) תמיד זמין, כך שהמסלול ”לפלטפורמה אין פרימיטיב נעילה - שגיאה קטלנית“ המתועד במקור לעולם אינו מופעל הלכה למעשה.

ראו גם#

  • fcntl - נעילת טווח־בתים וייעוצית עם שליטה עדינה יותר; יש להשתמש בה כאשר רזולוציה של קובץ שלם גסה מדי או כאשר מערכות קבצים רשתיות דורשות זאת

  • open - מפיק את מטפל הקובץ ש־flock פועל עליו; יש לזכור שמצב הפתיחה הנדרש (< מול >) חשוב עבור LOCK_SH / LOCK_EX שמדומים ב־fcntl

  • close - משחרר כל flock שעדיין מוחזק על המטפל

  • seek - יש לשלב עם flock בעת צירוף לקבצים שתהליכים אחרים עשויים להאריך בעוד אתה ממתין לנעילה

  • fork - ירושת נעילה בין תהליכי ילד תלויה בפרימיטיב הנעילה התשתיתי

  • Fcntl - מקור הקבועים LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB ותג הייבוא :flock