כל הזכויות שמורות © לצבי מלמד, 2014-2023

תרגול והבנה של MUTEX ואיך להשתמש בהם עם THREADS

המטרה במשימה הזאת, להבין מצבי תחרות שעלולים להיווצר בסביבה שבה פתילים מרובים פועלים על אותם מבני נתונים, וכיצד משתמשים ב MUTEX בכדי להבטיח פעולה תקינה.

הציור הבא מדגים מצב מירוץ "מאוד לא רצוי".

שני פתילים מבצעים הפקדה של כסף לאותו חשבון בנק. אם קורה תזמון מסוים של סדר הפעולות ביניהן, כפי שמודגם בדיאגרמה, אזי "כסף הולך לאיבוד".

הדוגמא הזאת ממחישה מצבי מירוץ, כאשר שניים או יותר פתילים קוראים וכותבים לאזור משותף בזיכרון.

הכנות למעבדה - הורדה ופתיחה של קובץ ה- tar

אם כבר ביצעת את ההורדה במשימה א' של פתילים דלג על שלב זה. שימי לב רק שאת נמצאת בתיקייה lab06/thread_mutex.

הורד את הקובץ lab06.tar

לאחר מכן, העתק את הקובץ הזה לתיקיה כלשהי, למשל לתיקיית labs ואז פתח אותו. (הפקודות הבאות מבצעות את הפתיחה)

cd labs
tar -xvf lab06.tar
# the directories lab06, lab06/thread_basic and lab06/thread_mutex should have been created
cd lab06/thread_mutex

שלב א': הרצה של התכנית ללא MUTEX.

התכנית שתשמש אותנו בתרגיל זה היא thread_list.c.

תיאור קצר של התכנית

המטרה בתכנית הזאת היא ליצור רשימה מקושרת שמכילה מספרים, מ- 1 עד 10, כאשר כל מספר "שוכן" באיבר כלשהו של הרשימה.
לאחר יצירת הרשימה, הפתיל הראשי מדפיס את הרשימה, ובכך התהליך מסתיים.

הפתיל הראשי יוצר שני פתילים. הפונקציה שבה הם מתחילים את חייהם נקראת add_items. היא מקבלת (לכאורה) שני ארגומנטים: מספר התחלה from ומספר סיום untill.

שאלה: כיצד פונקציה של פתיל מקבלת "שני נתונים"? הרי החתימה שלה מכילה ארגומנט אחד בלבד מטיפוס מצביע ל VOID? ==> עיון מאוד זריז בקוד מגלה את התשובה.

הפונקציה add_items מבצעת לולאה פשוטה, ועבור כל מספר שהיא צריכה להכניס לרשימה היא קוראת לפונקציה add_one_item.

הפונקציה add_one_item מכילה קטע קריטי בתכנית (החלק שמכניס את האיבר לרשימה).

היא מייצרת איבר ע"י malloc, ומוסיפה אותו בראש הרשימה. (ייצור האיבר איננו קטע קריטי, אלא רק ההכנסה).

בכדי להדגים מצב מירוץ, הוכנסו השהיות עם מידה של אקראיות, בתום הפונקציה הזאת.

בצעו את הדברים הבאים:
  1. ַעיינו בזריזות בקוד התכנית
  2. קמפל והרץ.

    האם הפלט תקין? האם הוא תואם את מה שחשבתם?
  3. ניתן לראות שלמעשה הפתילים לא רצים במקביל. פתיל אחד "ישן" ועד שהוא מתעורר, הפתיל האחר סיים את עבודתו.

    השורה שאחראית על כך היא נמצאת בתחילה הפונקציה add_items. הכנס אותה להערה.

  4. קמפל והרץ.

    האם הפלט תקין?

אנו רואים שנוצר מצב מירוץ - הרשימה איננה טובה - ישנם איברים "שהלכו לאיבוד". נרצה לתקן את זה בעזרת mutex


שלב ב': הכנסת MUTEX.

להלן שלבים לשימוש ב MUTEX. בצעו אותם:
  1. ַהכריזו על משתנה גלובלי
  2. pthread_mutex_t mtx; 
.

  • בתוך הפונקציה הראשית, לפני יצירת הפתילים, עלינו לאתחל את אובייטק ה MUTEX.

    הכניסו קריאה ל malloc שתקצה את האובייקט, ותציב את המצביע לתוך המשתנה mtx.

    בצעו קריאה שתאתחל אותו לערכי ברירת מחדל (בגלל ה NULL זה ברירת מחדל):
  •   pthread_mutex_init(&mtx, NULL); 
    
    .
  • בתוך הפונקציה הראשית, אחרי המיזוג join עם הפתילים, נכניס קריאות שמשחררות את המשאבים של ה MUTEX:

  •   pthread_mutex_destroy(&mtx);
    
    .
  • הכנסת הנעילה והשחרור זה הלב של העבודה. עלינו להכניס את שתי הקריאות הבאות:
  •   pthread_mutex_lock(&mtx);    // get the lock
      
      pthread_mutex_unlock(&mtx);  // release the lock 
    
    .

    חשוב להקפיד על הכלל הבא: אנחנו רוצים לבצע את הנעילה למשך פרק הזמן המינימלי שדרוש.

    כלומר, עלינו לעטוף את מה שהוא "הקטע הקריטי" - אבל לא מעבר לכך.

    הסיבה לכך: ככל שאנחנו נועלים קטע גדול יותר, אנחנו פוגעים במקביליות.

  • בהנחה שביצעתם את השינויים בקוד - קמפלו והריצו.

    הפלט צריך להיות תקין עכשיו.