שונות

lock#

מציב מנעול מייעץ על משתנה משותף, מערך, האש או תת־שגרה עד שהמנעול יוצא מהתחום.

lock הוא פרימיטיב הסנכרון של מודל ה־interpreter-threads של Perl (use threads; use threads::shared). כאשר THING הוא משתנה שהוצהר כ־:shared - או הפניה לאוסף משותף - lock רוכש mutex מייעץ על אותו נתון לכל שאר הבלוק העוטף. כאשר הבלוק יוצא, המנעול משוחרר. כל thread אחר הקורא ל־lock על אותו נתון נחסם עד שהמחזיק משחרר אותו.

המנעול הוא מייעץ: הוא עוצר threads אחרים שגם הם קוראים ל־lock על אותו משתנה, ולא יותר מזה. thread שרק קורא או כותב את המשתנה המשותף מבלי לנעול אותו אינו נחסם ואינו מסודר בתור. lock הוא מוסכמה, לא מחסום.

תקציר#

lock $shared_scalar
lock @shared_array
lock %shared_hash
lock &shared_sub
lock $ref_to_shared_aggregate

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

הארגומנט עצמו:

  • עבור סקלר, ערך הסקלר.

  • עבור מערך, האש, או תת־שגרה, הפניה אליו.

ערך ההחזרה משמש לעיתים נדירות. lock נקרא בשל תופעת הלוואי שלו (רכישת ה־mutex), לא בשל ערכו.

המנעול הוא בעל־תחום, לא בעל־תחום־הוראה#

המנעול משוחרר כאשר הבלוק העוטף הפנימי ביותר יוצא, לא כאשר הוראת ה־lock מסתיימת. שתי השלכות:

  • lock בתוך בלוק { ... } מסדר בתור רק למשך אותו בלוק. יציאה מהבלוק - על־ידי ירידה, return, last, die, או כל אופן אחר - משחררת את המנעול.

  • לא ניתן לשחרר מנעול מוקדם. אין unlock. אם אתם זקוקים למקטע קריטי קצר יותר, הציגו בלוק { ... } הדוק יותר:

    {
        lock %shared_hash;
        $shared_hash{$key} = $value;
    }   # lock released here
    do_unlocked_work();
    

מילת מפתח חלשה#

lock הוא מילת מפתח חלשה. אם תת־שגרה בשם lock נמצאת בתחום בנקודת הקריאה (הוצהרה או יובאה לפני אתר הקריאה), אותה תת־שגרה נקראת במקום ה־built-in. זה הופך את lock לבטוח לשימוש כשם מתודה או פונקציה בקוד שאינו משתמש ב־threads. כדי לכפות את ה־built-in כאשר תת־שגרה באותו שם נמצאת בתחום, קראו לו דרך החבילה שלו: CORE::lock($thing).

דוגמאות#

סידור בתור של עדכון האש בין threads:

use threads;
use threads::shared;

my %counts :shared;

sub bump {
    my $key = shift;
    lock %counts;
    $counts{$key}++;
}

נעילת אוסף דרך הפניה. lock עוקב אחר רמת הפניה אחת כדי למצוא את הנתון לנעילה:

my @queue :shared;
my $queue_ref = \@queue;

sub enqueue {
    my $item = shift;
    lock $queue_ref;        # locks @queue
    push @queue, $item;
}

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

my $result;
{
    lock %cache;
    $result = $cache{$key};
}
expensive_post_processing($result);   # runs without the lock

ללא use threads::shared, lock הוא no-op. זה הופך אותו ללא־מזיק להשארה בקוד שעלול לרוץ ב־single-thread:

# no `use threads::shared` here
lock $x;                    # does nothing, does not warn

מקרי קצה#

  • משתנה לא־משותף: lock על משתנה שלא הוצהר כ־:shared ואינו הפניה לנתונים משותפים אינו עושה דבר שימושי. תחת upstream זה עלול לבצע croak במקרים מסוימים; באחרים זה no-op בשקט. אל תסתמכו על lock כדי לתפוס את הבאג ״שכחתי לשתף״.

  • הפניה לעומת אוסף: lock \%h ו־lock %h שניהם נועלים את %h. lock מבצע dereference ברמה אחת כדי למצוא את הנתון המשותף.

  • נעילה רקורסיבית בתוך thread אחד: אותו thread עשוי לרכוש מחדש מנעול שהוא כבר מחזיק; ה־mutex הוא רקורסיבי לכל thread. thread אחר עדיין נחסם.

  • אין unlock: שחרור לפני יציאה מבלוק מצריך מבנה מחדש לתחום הדוק יותר. תבניות undef $lock_guard משפות אחרות אינן חלות - lock אינו ערך שמאחסנים.

  • עמימות של מילת־מפתח חלשה: sub lock שהוצהר או יובא מוקדם יותר מנצח את ה־built-in. השתמשו ב־CORE::lock כדי לבטל את העמימות.

  • לא חוצה־תהליכים: lock מתאם threads בתוך תהליך מפענח אחד. לנעילה ברמת קובץ בין תהליכים השתמשו ב־flock; למנעולים מייעצים של מערכת קבצים אלה מנגנונים נפרדים ללא אינטראקציה.

הבדלים מ־upstream#

  • pperl אינו מממש interpreter threads. ה־opcode של lock מזוהה על־ידי המפענח אך אין לו השפעת זמן ריצה - הוא מתנהג בדיוק כפי ש־lock של upstream מתנהג בבנייה ללא threads::shared: no-op שקט המחזיר את הארגומנט שלו. קוד המשתמש ב־lock באופן הגנתי (כפי שתיעוד ה־upstream ממליץ לתאימות single-thread) רץ ללא שינוי. קוד התלוי ב־lock לסנכרון אמיתי לא יסונכרן תחת pperl.

  • ההפצה הנלווית threads::shared אינה זמינה תחת pperl; use threads::shared נכשל בזמן הידור. מקביליות ב־pperl נחשפת דרך אוטו־מקביליות של JIT (Rayon) על צורות לולאה ספציפיות, לא דרך ithreads גלויים למשתמש.

ראו גם#

  • flock - מנעול מייעץ על מטפל קובץ, מתאם בין תהליכים ולא בין threads

  • wait - אוסף תהליך־ילד; המקבילה ברמת התהליך להמתנה ליחידת ביצוע אחרת

  • fork - פרימיטיב המקביליות האחר ב־Perl הליבתי, ללא זיכרון משותף כברירת מחדל

  • our - הצהרה על משתנה בתחום־חבילה; משויך בדרך כלל ל־:shared בקוד מבוסס־threads

  • local - קישור תחום־דינמי, לעיתים מבולבל עם lock משום ששניהם מוגבלים־תחום ושניהם משחזרים ביציאה מבלוק