# 13-14 שבוע : פרק Process Synchronization סינכרון בין תהליכים ` חלק א

‫שבוע ‪#13-14‬‬
‫פרק‪Process Synchronization :‬‬
‫סינכרון בין תהליכים‬
‫חלק א'‬
‫קורס מערכות הפעלה א'‬
‫מכללת הדסה ‪ /‬מכללה חרדית‬
‫צבי מלמד‬
‫‪[email protected]‬‬
‫הרצאות הקורס מבוססות במידה רבה ביותר על ההרצאות של ד"ר יורם ביברמן‬
‫© כל הזכויות שמורות לד"ר יורם ביברמן ולצבי מלמד‬
‫©צבי מלמד‬
‫‪1‬‬
‫‪a cooperating process‬‬
‫• תהליך משתף‪-‬פעולה )‪(a cooperating process‬‬
‫– תהליך שעשוי להשפיע או להיות מושפע מתהליכים אחרים‬
‫במערכת‪.‬‬
‫– עשוי לחלוק מרחב כתובות )בפרט נתונים( עם אחרים‬
‫– עשוי להעביר מידע דרך ערוצי תקשורת עם עמיתים‪.‬‬
‫• בפרק זה‪ ,‬כשנדבר על תהליך אנו נתכוון גם לפתיל‬
‫– שיתוף נתונים עם פתילים אחרים רלבנטי במיוחד לגבי פתילים‬
‫©צבי מלמד‬
‫‪2‬‬
‫רקע ‪Background‬‬
‫•‬
‫תיפעול זוג תהליכים‪ :‬יצרן וצרכן‬
‫– מעבירים זה לזה הודעות‬
‫– באמצעות זיכרון משותף המנוהל כתור‬
‫– מימוש במערך מעגלי בגודל ‪ BUFFER_SIZE‬תאים‬
‫•‬
‫אופן הפעולה‪:‬‬
‫– היצרן מוסיף איברים לתור‪ ,‬והצרכן גורע‪.‬‬
‫•‬
‫משתני עזר‪:‬‬
‫– מונה )‪ (counter‬המציין כמה איברים מצויים בתור בכל נקודת זמן‪ .‬היצרן‬
‫מגדיל את המונה‪ ,‬הצרכן מקטין‪.‬‬
‫– המשתנה ‪ in‬מציין לאן מכניס היצרן את האיבר הבא‪,‬‬
‫– המשתנה ‪ out‬יציין מהיכן יצרוך הצרכן את האיבר הבא‪.‬‬
‫– ‪ in, out‬מאותחלים לאפס‪.‬‬
‫©צבי מלמד‬
‫‪3‬‬
‫מבוא )דוגמא( – קוד היצרן‬
‫•‬
‫תיאום‪:‬‬
‫– כאשר התור מלא על היצרן להמתין‪ ,‬כאשר התור ריק על הצרכן להמתין‪.‬‬
‫•‬
‫קוד היצרן‪:‬‬
‫{ )‪while (1‬‬
‫‪while (counter == BUFFER_SIZE) ; // busy wait‬‬
‫; ‪buffer[in] = new_item‬‬
‫; ‪in = (in +1) % BUFFER_SIZE‬‬
‫; ‪counter++‬‬
‫}‬
‫©צבי מלמד‬
‫‪4‬‬
‫מבוא )דוגמא( – קוד הצרכן‬
:‫• קוד הצרכן סימטרי‬
while (1) {
while (counter == 0) ; //busy wait
new_item
= buffer[out] ;
out = (out +1) % BUFFER_SIZE ;
counter-- ;
}
5
‫©צבי מלמד‬
‫דוגמת מבוא – תיזמון אפשרי ותוצאתו‬
‫•‬
‫עתה נניח שהתור מכיל ‪ 17‬פריטים‪.‬‬
‫היצרן והצרכן פונים אליו במקביל‪,‬‬
‫בפרט מתפעלים במקביל את ‪:counter‬‬
‫האחד מגדיל את ערכו‪ ,‬השני מקטינו‪.‬‬
‫•‬
‫נניח שמערכת ההפעלה מתזמנת את‬
‫הפעולות באופן הבא )תוך שהמעבד‬
‫מבצע לסירוגין כל תהליך(‬
‫התוצאה‪ :‬ערכו של המונה הוא ‪16‬‬
‫)ולא ‪ 17‬כפי שראוי(‪.‬‬
‫הסיבה‪ :‬שני התהליכים פנו למשתנה‬
‫המשותף במקביל‪ ,‬ותפעלו אותו‬
‫באופן לא מתואם\מסונכרן‬
‫צרכן‬
‫יצרן‬
‫‪reg1 = counter‬‬
‫)‪(reg117‬‬
‫‪reg2 = counter‬‬
‫)‪(reg217‬‬
‫‪reg1++‬‬
‫)‪(reg118‬‬
‫‪reg2-‬‬‫)‪(reg216‬‬
‫‪counter = reg1‬‬
‫)‪(counter18‬‬
‫‪counter = reg2‬‬
‫)‪(counter16‬‬
‫©צבי מלמד‬
‫‪6‬‬
‫מבוא )המשך(‬
‫• תקלה דומה אך שונה‪:‬‬
‫– עדכונו של המשתנה )על‪-‬פי ערכו של אוגר מתאים( ע"י תהליך‬
‫א' לוקח יותר ממחזור זיכרון יחיד‬
‫– בין תחילת העדכון לסיומו מתעניין תהליך ב' בערכו של‬
‫המשתנה‪ ,‬ועל כן מקבל ערך שגוי‪.‬‬
‫©צבי מלמד‬
‫‪7‬‬
‫מצבי מירוץ ‪Race Condition -‬‬
‫• מצבי מרוץ )‪ :(race condition‬מצב שבו התוצאה תלויה בתזמון‬
‫המקרי בו המחשב הריץ את התהליכים )ולכן התוצאה עלולה‬
‫להיות שגויה(‪.‬‬
‫• הפתרון‪ :‬מנגנון שיאפשר רק לתהליך יחיד לפנות למשתנה ‪counter‬‬
‫בפרק זמן כלשהו‪.‬‬
‫• הנושא הזה באופן כללי נקרא‪:‬‬
‫תיאום בין תהליכים או ‪process synchronization‬‬
‫©צבי מלמד‬
‫‪8‬‬
‫מנעול ‪lock‬‬
‫•‬
‫•‬
‫•‬
‫פתרון אפשרי לבעיה‪ :‬מנעול )‪.(lock‬‬
‫– רק תהליך יחיד יכול לנעול את המנעול בכל יחידת זמן‪.‬‬
‫– על המנעול מוגדרות פעולות אטומיות של נעילה ושחרור‬
‫נעילה\רכישה )‪:(lock/acquire‬‬
‫– תהליך המבצע פעולה זו נחסם עד שהמנעול מתפנה‪ ,‬ואז הוא יכול להתקדם‬
‫– מובטח שהוא התהליך היחיד שרכש את המנעול )ועל כן היחיד שפונה‬
‫למשתנה(‬
‫פתיחה\שחרור )‪:(unlock/release‬‬
‫– שחרור המנעול‪ ,‬כך שתהליך אחר יוכל לרכוש )לנעול ( אותו‪.‬‬
‫– הטיפול במשתנים המשותפים‪ ,‬אליהם נרצה לאפשר גישה בלבדית‬
‫)‪ (exclusive‬יחייב ראשית רכישת המנעול‪.‬‬
‫– בתום הטיפול במשתנים ישוחרר המנעול‪.‬‬
‫©צבי מלמד‬
‫‪9‬‬
‫מנעול ‪lock‬‬
‫• עבודה עם המנעול באופן הזה תבטיח שלא ייווצר מצב מרוץ‪.‬‬
‫• רעיון אפשרי‪:‬‬
‫– נגדיר משתנה בולאני שיהווה את המנעול‪.‬‬
‫– המשתנה עשוי להיות פתוח או סגור )‪(true or false‬‬
‫©צבי מלמד‬
‫‪10‬‬
‫בעיה ביישום פתרון המנעול‬
‫•‬
‫בכדי לרכוש את המנעול מבצע‬
‫התהליך שתי פעולות‪:‬‬
‫תהליך ב'‬
‫קרא את מצב‬
‫המנעול‬
‫– )א( בדיקת ערכו של המנעול‬
‫– )ב( עדכון ערכו‬
‫קרא את מצב‬
‫המנעול‬
‫– יש חשש למצב הבא )נניח‬
‫שהמנעול פתוח(‪:‬‬
‫– ‪‬ולא השגנו בלבדיות בפניה‬
‫למשתנים המשותפים‪.‬‬
‫תהליך א'‬
‫אם ערכו פתוח אזי‬
‫עדכן את ערכו‬
‫לסגור‬
‫אם ערכו פתוח אזי‬
‫עדכן את ערכו‬
‫לסגור‬
‫טפל במשתנים‬
‫המשותפים‬
‫טפל במשתנים‬
‫המשותפים‬
‫©צבי מלמד‬
‫‪11‬‬
‫בעיה ביישום פתרון המנעול‬
‫מסקנה‪:‬‬
‫• מנעול הוא רעיון יפה‪ ,‬אבל צריך דרך )יותר( מעודנת‪ ,‬מתוחכמת‬
‫לממשו מאשר משתנה בוליאני פשוט שערכו נבדק ובמידת האפשר‬
‫מעודכן‬
‫©צבי מלמד‬
‫‪12‬‬
‫פתרונות לבעית מימוש המנעול‬
‫• שלושה סוגי פתרונות לבעיה כיצד לממש מנעול ולהשיג בלבדיות בטיפול‬
‫במשתנים‪:‬‬
‫– פתרונות תכנה‪:‬‬
‫• פרוטוקולים )=אוסף כללים( שיקבעו כיצד על התהליכים לנהוג ע"מ‬
‫שתושג הבלבדיות‪.‬‬
‫– פתרונות חומרה‬
‫• מסייעים להשיג את התוצאה הרצויה‪.‬‬
‫– פתרונות תכנה מרמה גבוה‬
‫• מבני נתונים ופעולות מרמה גבוהה‪,‬שית ַ מכו ע"י הקומפיילר‪ ,‬ויאפשרו‬
‫להשיג את הבלבדיות בנוחות‬
‫• מימוש הפעולות על ידי הקומפיילר‪) ,‬בד"כ תוך שימוש במנגנוני חומרה(‬
‫©צבי מלמד‬
‫‪13‬‬
‫בעיית הקטע הקריטי‬
‫‪The Critical-Section Problem‬‬
‫•‬
‫•‬
‫•‬
‫נניח מערכת בת ‪ n‬תהליכים‪ .‬לכל אחד מהתהליכים יש קטע קריטי שבו הוא פונה‬
‫למשתנים או קבצים משותפים ועליו לעשות זאת באופן בלבדי‪.‬‬
‫משמעות המונח קטע קריטי‪:‬‬
‫– לכל אחד מ‪ n-‬התהליכים יש "קטע קריטי"‪ .‬זהו קטע שבו הם ניגשים‬
‫למשאבים משותפים )למשל משתנים או קבצים(‪.‬‬
‫– בגלל בעיות – כגון בעיות מירוץ‪ ,‬אסור שיותר מתהליך אחד ימצא בקטע‬
‫הקריטי שלו בו זמנית‪.‬‬
‫– כלומר‪ ,‬לכל היותר אחד מתוך ‪ n‬התהליכים מבצע בכל נקודת זמן נתונה את‬
‫הקטע הקריטי שלו‬
‫– התהליכים האחרים יבצעו וימצאו באזורים אחרים של התוכנית‪ .‬כלומר או‬
‫שהם ימתינו להכנס לקטע הקריטי או שהם יבצעו את "מה שנשאר" – קטע‬
‫שיורי )‪(remainder section‬‬
‫בעיית הקטע הקריטי‪:‬‬
‫– מה יהיה הפרוטוקול )כללי ההתנהגות( באמצעותו נממש קטע קריטי‪.‬‬
‫©צבי מלמד‬
‫‪14‬‬
‫בעיית הקטע הקריטי‬
‫‪The Critical-Section Problem‬‬
‫• הפרוטוקול יכלול‪:‬‬
‫• קטע כניסה )‪(entry section‬‬
‫– סדרת פעולות אותן צריך לבצע תהליך המעוניין להיכנס‬
‫לקטע הקריטי שלו‪.‬‬
‫• קטע יציאה )‪(exit section‬‬
‫– סדרת פעולות אותן יבצע התהליך בסיום ביצוע הקטע‬
‫הקריטי שלו‬
‫• קטע שיורי )‪(remainder section‬‬
‫– קוד שאינו מטפל ב ַ משתנים המשותפים‬
‫©צבי מלמד‬
‫‪15‬‬
‫בעיית הקטע הקריטי‬
The Critical-Section Problem
16
‫©צבי מלמד‬
‫שלוש הדרישות לבעיית הקטע הקריטי‬
‫•‬
‫בלבדיות )‪:(mutually exclusiveness‬‬
‫– רק תהליך יחיד יוכל לבצע את הקטע הקריטי שלו בכל נקודת זמן‪.‬‬
‫•‬
‫התקדמות )‪:(progress‬‬
‫– אם ‪ m‬תהליכים כלשהם מעוניינים להיכנס לקטע קריטי )כל תהליך לקטע‬
‫קריטי של עצמו(‪ ,‬ואף תהליך אינו מצוי בקטע קריטי‪ .‬אזי תוך זמן סופי‬
‫אחד המעוניינים יזכה להיכנס לקטע קריטי = אין קיפאון‪.‬‬
‫•‬
‫המתנה חסומה )‪ ,(bounded waiting‬אי הרעבה‬
‫– יש חסם על מספר הפעמים שתהליכים אחרים רשאים להיכנס לקטע קריטי‪.‬‬
‫שלהם אחרי שתהליך ‪ P‬ביקש להיכנס לקטע קריטי‪ .‬שלו‪ ,‬ועד שבקשתו‬
‫מסופקת= הוגנות‬
‫©צבי מלמד‬
‫‪17‬‬
‫פתרונות תכנה לזוג תהליכים בלבד‬
‫• ‪Two-Processes Solutions‬‬
‫• נניח זוג תהליכים‪ P0, P1 :‬בלבד‪.‬‬
‫• נניח ‪ -‬כי כל פעולת מכונה‪ ,‬בפרט השמה‪ ,‬היא אטומית‬
‫– )כלומר מתבצעת בשלמותה בלא שניתן יהיה לראות 'תוצאת‬
‫ביניים' של הפעולה(‪.‬‬
‫– הנחה זאת איננה לגמרי מוצדקת‬
‫• הערה‪ :‬במחשבים מודרניים פעולת השמה‪/‬פקודת מחשב עשויה‬
‫להימשך מספר מחזורי זיכרון‪ ,‬ועל‪-‬כן עלול לקרות מצב בו‪ :‬תהליך‬
‫א' מתחיל לעדכן משתנה‪ ,‬תהליך ב' קורא ערך לא תקין של‬
‫המשתנה‪ ,‬תהליך א' מסיים את עדכון ערך המשתנה‬
‫©צבי מלמד‬
‫‪18‬‬
‫פרוטוקול א'‬
‫• התהליכים יחלקו משתנה משותף‪turn :‬‬
‫– כאשר ערכו אפס רשאי ‪ P0‬להיכנס לקטע הקריטי‬
‫– כאשר ערכו אחד‪ ,‬רשאי ‪ P1‬להיכנס לקטע הקריטי‬
‫• המשתנה יאותחל לערך כלשהו‪.‬‬
‫{ )‪while (1‬‬
‫‪while (turn != 0) ; // busy wait‬‬
‫הקטע הקריטי‬
‫; ‪turn = 1‬‬
‫הקוד של‬
‫תהליך ‪P0‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪19‬‬
‫בדיקת תכונות פרוטוקול א'‬
‫• נבדוק אילו משלוש הדרישות שהצגנו הפרוטוקול משיג‪:‬‬
‫– בלבדיות ‪‬‬
‫מושגת‪.‬‬
‫אינה בהכרח מושגת‪ P0 :‬לא יוכל להיכנס‬
‫– התקדמות ‪‬‬
‫לקטע הקריטי שלו שוב‪ ,‬עד ש‪ P1 -‬ייכנס לקטע הקריטי שלו‪.‬‬
‫– המתנה חסומה ‪ ‬מושגת‪ :‬אם שני התהליכים רוצים להיכנס‬
‫לקטע הקריטי אזי הם ייכנסו לסירוגין‪.‬‬
‫©צבי מלמד‬
‫‪20‬‬
‫הבעיה של פרוטוקול א'‬
‫• פרוטוקול א' לא התעניין בשאלה‪ :‬האם התהליך האחר בכלל‬
‫מעוניין להיכנס לקטע הקריטי‬
‫• ההנחה‪ :‬הקטע הקריטי מבוצע לסירוגין‬
‫• התור הועבר לתהליך ‪ P0‬או ‪ P1‬בכל מקרה‬
‫• על כן עתה נציע שיפור‪ ,‬נגדיר את המערך‪:‬‬
‫• ; }‪bool want[2] = {false, false‬‬
‫• ‪ want[i] == true‬משמע התהליך ‪ #i‬מעוניין להיכנס לק‪.‬ק‪.‬‬
‫©צבי מלמד‬
‫‪21‬‬
‫הבעיה של פרוטוקול א'‬
‫• פרוטוקול א' לא התעניין בשאלה‪" :‬האם התהליך האחר בכלל‬
‫מעוניין להיכנס לקטע הקריטי"‬
‫• ההנחה‪ :‬הקטע הקריטי מבוצע לסירוגין‬
‫• התור הועבר לתהליך ‪ P0‬או ‪ P1‬בכל מקרה‬
‫©צבי מלמד‬
‫‪22‬‬
‫פרוטוקול ב'‬
‫• נגדיר את המערך‪:‬‬
‫; }‪bool want[2] = {false, false‬‬
‫• ‪ want[i] == true‬משמע התהליך ‪ #i‬מעוניין להיכנס‬
‫לקטע הקריטי‬
‫{ )‪while (1‬‬
‫‪// I want to enter‬‬
‫; ‪want[0] = true‬‬
‫‪// I wait to my pal‬‬
‫;)]‪while (want[1‬‬
‫הקטע הקריטי‬
‫; ‪want[0] = false‬‬
‫הקוד של תהליך ‪P0‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪23‬‬
‫בדיקת תכונות פרוטוקול ב'‬
‫• בלבדיות נשמרת‪ :‬אם חברי רוצה להיכנס אני ממתין‪.‬‬
‫• התקדמות‪ :‬עלולה שלא להתקיים‪) .‬ראה הדוגמא(‬
‫‪P0‬‬
‫‪P1‬‬
‫‪want[0]=true‬‬
‫התוצאה‪/‬מסקנה‪:‬‬
‫התהליכים בחסימה הדדית‬
‫‪want[1]=true‬‬
‫;]‪while (want[1‬‬
‫מתקיים‬
‫;]‪while (want[0‬‬
‫מתקיים‬
‫©צבי מלמד‬
‫‪24‬‬
'‫פרוטוקול ג‬
:‫משתנים‬
bool want[2]={false, false};
int turn = 0;
while (1) {
want[0] = true ;
turn = 1 ;
// I want to enter as a gentlemen
// I allow my pal 2 be the 1st
while(want[1] && turn == 1) ;
// I wait while it is his turn
// AND he also wants to enter
‫הקטע הקריטי‬
want[0] = false ;
P0 ‫הקוד של תהליך‬
‫הקטע השיורי‬
{
25
‫©צבי מלמד‬
•
‫פרוטוקול ג' – בדיקת בלבדיות ‪ +‬התקדמות‬
‫• נניח ששני התהליכים מעוניינים להיכנס‬
‫– שניהם מבצעים‪want[i] = true:‬‬
‫– השני מביניהם שיבצע‪ turn = other-process :‬יציב אחרון ערך‬
‫ל‪ turn -‬לפני לולאת ההמתנה‪ ,‬וזה יהיה הערך שיוותר במשתנה‬
‫– ולכן תהליך זה לא יכנס לקטע הקריטי‬
‫– אבל משנהו כן ייכנס‪ ,‬וייכנס לבד‪.‬‬
‫– אם רק אחד מעוניין להיכנס )זאת אומרת שהשני נמצא בקטע‬
‫השיורי( רואים שהוא יוכל להיכנס‪ ,‬וברור שהוא נכנס לבד‬
‫©צבי מלמד‬
‫‪26‬‬
‫פרוטוקול ג' – בדיקת הוגנות‬
‫• השני שהזין ערך למשתנה הוא האיטי יותר‬
‫• עמיתו המהיר ייכנס לקטע הקריטי‪ ,‬יצא‪ ,‬יסמן שאינו מעוניין‬
‫להיכנס‪ ,‬ונניח שאף יספיק לסמן שהוא שוב מעוניין להיכנס‪.‬‬
‫• מיד לאחר מכן‪ ,‬הזריז יעניק את התור ל ַאיטי‪ ,‬ובכך ייתקע את‬
‫עצמו‪ .‬עד מתי?‬
‫• עד שהאיטי ייכנס‪ ,‬יצא‪ ,‬ויסמן שאינו מעוניין;‬
‫• לחילופין‪ ,‬אם האיטי פתאום הפך נורא זריז אזי הוא‪ :‬יסמן שאינו‬
‫מעוניין‪ ,‬שהוא שוב מעוניין‪ ,‬ושעתה תורו של חברו‪.‬‬
‫• בקיצור‪ :‬מי שיצא בהכרח מעניק התור למשנהו‪.‬‬
‫©צבי מלמד‬
‫‪27‬‬
‫• מצאנו פתרון לבעית הקטע‬
‫הקריטי של שני תהליכים‬
‫• בדקנו וראינו שהוא מקיים את‬
‫שלושת הדרישות‪ :‬בלבדיות‪,‬‬
‫התקדמות‪ ,‬ואי‪-‬חסימה‪/‬הוגנות‬
‫פרוטוקול ג' ‪ -‬סיכום‬
‫•‬
‫משתנים‪:‬‬
‫;}‪bool want[2]={false, false‬‬
‫;‪int turn = 0‬‬
‫{ )‪while (1‬‬
‫‪// I want to enter as a gentlemen‬‬
‫‪// I allow my pal 2 be the 1st‬‬
‫; ‪want[0] = true‬‬
‫; ‪turn = 1‬‬
‫; )‪while(want[1] && turn == 1‬‬
‫‪// I wait while it is his turn‬‬
‫‪// AND he also wants to enter‬‬
‫הקטע הקריטי‬
‫; ‪want[0] = false‬‬
‫הקטע השיורי‬
‫הקוד של תהליך ‪P0‬‬
‫{‬
‫©צבי מלמד‬
‫‪28‬‬
‫פתרונות תכנה לתהליכים רבים‬
‫‪Multi Process Solutions‬‬
‫•‬
‫מעודדים מהצלחתינו למצוא פתרון תוכנה לבעית הקטע הקריטי‬
‫לשני תהליכים – ניגש להתמודד עם האתגר הבא‪ :‬פתרון תוכנה‬
‫לאותה בעיה – אבל הפעם‪ ,‬עבור תהליכים מרובים – ‪ n‬תהליכים‬
‫•‬
‫נכיר שני פתרונות‪:‬‬
‫‪ (1‬אלגוריתם )פרוטוקול( המאפיה‬
‫‪ (2‬מימוש מנעולים באמצעות תכנה‬
‫©צבי מלמד‬
‫‪29‬‬
‫אלג' המאפיה ‪Bakery Algorithm‬‬
‫• מקור השם‪ :‬מדוכני מזון מהיר בהם מופעל האלגוריתם‪:‬‬
‫– מי שמגיע מקבל מספר – המספר הבא בתור )פחות או יותר‪( ...‬‬
‫– למה פחות או יותר?‬
‫כי במציאות זה עובד בקלות ופשטות‪...‬‬
‫אבל בתוכנה‪ ,‬אני בודק מה המספר הכי גבוה עד כה‪ ,‬ומוסיף אחד –‬
‫זהו המספר שלי‬
‫אבל במקביל אלי‪ ,‬אולי עוד מישהו עשה תהליך כזה‪ ,‬ואז לשנינו יש‬
‫אותו מספר‬
‫©צבי מלמד‬
‫‪30‬‬
‫אלג' המאפיה ‪Bakery Algorithm‬‬
‫• האלגורתים מניח‪:‬‬
‫– לכל תהליך קיים מזהה ייחודי בתחום ‪.0..n-1‬‬
‫• האלג' עשוי להיות מורץ במערכת מקבילית )בת כמה מעבדים(‪.‬‬
‫• סימונים‪:‬‬
‫– )‪ (a, b) < (c, d‬על‪-‬פי יחס הסדר הלקסיקוגרפי 'הרגיל'‪.‬‬
‫– )‪ max(a0, … ,an-1‬מחזירה ערך הגדול או שווה מכל ה‪ai -‬‬
‫)‪.(i=0, …, n-1‬‬
‫©צבי מלמד‬
‫‪31‬‬
‫אלגוריתם המאפיה ‪Bakery Algorithm‬‬
‫• מבני נתונים‪:‬‬
‫; } ‪bool choosing[n] = { false‬‬
‫• ‪ -‬מציין האם התהליך נמצא כרגע בתהליך בחירת מספר לתור‬
‫)לביצוע הקטע הקריטי(‬
‫; }‪int number[n] = {0‬‬
‫• ‪ -‬מערך של "המספר בתור" של כל תהליך‬
‫©צבי מלמד‬
‫‪32‬‬
‫המספר אינו בהכרח‬
‫ייחודי‪ ,‬בגלל שתהליך‬
‫אחר במעבד )הזה או‬
‫מעבד אחר( עשוי‬
‫לבקש מספר במקביל‬
‫'אלי'‬
‫אלגוריתם המאפיה‬
‫{ )‪while (1‬‬
‫אני כרגע בוחר מספר ‪choosing[me] = true ; //‬‬
‫= ]‪number[me‬‬
‫; ‪max(number[0],...,number[n-1]) + 1‬‬
‫סיימתי לבחור ‪choosing[me] = false ; //‬‬
‫עבור "כל‬
‫האחרים"‬
‫אם מישהו כרגע בוחר‬
‫מספר‪ ,‬אני מחכה‬
‫שיסיים לבחור‬
‫כל עוד האחר רוצה‬
‫להיכנס‪ ,‬והוא בעדיפות‬
‫על‪-‬פני אני ממתין‬
‫{ )‪for (other = 0; other < n; other++‬‬
‫; )]‪while (choosing[other‬‬
‫&& ‪while (number[other] != 0‬‬
‫< )‪(number[other], other‬‬
‫; ) )‪(number[me], me‬‬
‫}‬
‫הקטע הקריטי‬
‫; ‪number[me] = 0‬‬
‫קטע שיורי‬
‫}‬
‫©צבי מלמד‬
‫‪33‬‬
‫פתרון שני באמצעות תכנה‬
‫• פתרון שני לבעיית התיאום )באמצעות תכנה(‪:‬‬
‫– מבוסס על ההנחה שהתוכנית יכולה לחסום פסיקות‪ ,‬ועל ידי‬
‫כך להבטיח שהמעבד לא ייגזל ממנה בין בדיקת תנאי להשמה‪.‬‬
‫– הערה‪ :‬אם הקוד מבוצע ע"י מערכת ההפעלה‪ ,‬למשל קריאת‬
‫מערכת שתבצע את זה – ההנחה הזאת איננה בלתי סבירה‪....‬‬
‫• )בתנאי ש‪ - ...‬איזה תנאי? – כלומר‪ ,‬באיזה מקרה‪ ,‬גם אם ניתן לחסום פסיקות‪,‬‬
‫זה עדיין לא יבטיח לנו את קיום התנאים הדרושים מהקטע הקריטי(‬
‫• על‪-‬סמך ההנחה נוכל לממש מנעולים‪ ,‬כפי שתוארו קודם לכן‪ ,‬באופן‬
‫הבא‪:‬‬
‫©צבי מלמד‬
‫‪34‬‬
‫חסימת פסיקות )פתרון שני באמצעות תכנה(‬
‫בדיקה‪ :‬כאשר המנעול פנוי‬
‫‪ .1‬חוסמים את הפסיקות‬
‫‪ .2‬בודקים ומגלים שהמנעול פנוי‬
‫)לא נכנסים ללולאה(‬
‫{ ) ‪lock_acquire( L‬‬
‫; ‪disable_interrupts‬‬
‫{ )‪while (L != free‬‬
‫; ‪enable_interrupts‬‬
‫; ‪disable_interrupts‬‬
‫}‬
‫‪ .3‬נועלים את המנעול‬
‫‪ .4‬ומאפשרים פסיקות‪.‬‬
‫•‬
‫; ‪L = busy‬‬
‫; ‪enable_interrupts‬‬
‫כלומר בין הבדיקה לנעילה‬
‫הפסיקות חסומות ולכן המעבד‬
‫לא ייגזל מאתנו‪.‬‬
‫}‬
‫©צבי מלמד‬
‫‪35‬‬
‫חסימת פסיקות )פתרון שני באמצעות תכנה(‬
‫בדיקה‪ :‬כאשר המנעול תפוס‬
‫‪ .1‬נכנסים ללולאה )שוב ושוב(‬
‫‪ .2‬חוסמים הפסיקות בסוף כל סיבוב )לפני‬
‫בדיקת התנאי בכניסה לסיבוב הבא(‬
‫{ ) ‪lock_acquire( L‬‬
‫; ‪disable_interrupts‬‬
‫{ )‪while (L != free‬‬
‫; ‪enable_interrupts‬‬
‫; ‪disable_interrupts‬‬
‫‪ .3‬לכן כאשר נבדק התנאי אחרי שהמנעול‬
‫שוחרר ‪ -‬הפסיקות תהיינה חסומות‪.‬‬
‫•‬
‫זהו התרחיש שאוזכר בשקף הקודם‬
‫•‬
‫בכל מקרה‪ :‬בין בדיקת ערך המנעול‪,‬‬
‫לנעילתו ‪ -‬הפסיקות חסומות‪.‬‬
‫}‬
‫; ‪L = busy‬‬
‫; ‪enable_interrupts‬‬
‫}‬
‫©צבי מלמד‬
‫‪36‬‬
‫חסימת פסיקות ‪ -‬מגבלות השיטה‬
‫• אם התכנית עפה כאשר הפסיקות חסומות צריך לדאוג לאפשר‬
‫אותן‬
‫• חסימת פסיקות משמעותה עיכוב הטיפול בהן‪.‬‬
‫– לכל הפחות‪ ,‬החסימה כאן היא לפרק זמן קצר‪.‬‬
‫• הפתרון אינו ישים למערכת בת מספר מעבדים!!‬
‫– אין הגנה שתמנע ממעבד אחר לגשת למנעול )בזמן הקריטי בין‬
‫הנקודה שהוא נבדק ועד הנקודה שהוא ננעל(‬
‫©צבי מלמד‬
‫‪37‬‬
‫חומרת סינכרון ‪Synchronization Hardware‬‬
‫• רוב מערכות החומרה מספקות תמיכה לבעיית הסנכרון‬
‫• החומרה מספקת פקודת אסמבלר בודדת אשר מתבצעת באופן‬
‫אטומי!‬
‫• פקודה זאת לא יכולה להיקטע באמצעה‬
‫• הפקודה תבצע‪:‬‬
‫– בדיקת ערך של משתנה וגם‪...‬‬
‫– השמת ערך חדש למשתנה או פעולה שקולה אחרת‪.‬‬
‫• הרעיון דומה למה שראינו קודם – בפתרון חסימה של הפסיקות‪:‬‬
‫דואגים לכך ששום דבר לא יכול להתבצע בזמן שבין בדיקת המנעול‬
‫לבין שינוי ערכו לערך "נעול"‪.‬‬
‫©צבי מלמד‬
‫‪38‬‬
‫חומרת סינכרון ‪Synchronization Hardware‬‬
‫• הפקודה המוכרת ביותר בנושא זה נקראת ‪.TestAndSet‬‬
‫• להלן אבסטרקציה של מה שפקודת‪-‬המכונה הזאת מבצעת‪:‬‬
‫למשתנה מוכנס "בכל‬
‫מקרה" הערך נעול‬
‫{ )‪bool TestAndSet( bool &var‬‬
‫; ‪bool old_val = var‬‬
‫; ‪var = true‬‬
‫; ‪return old_val‬‬
‫}‬
‫מוחזר הערך הקודם של המנעול‬
‫©צבי מלמד‬
‫‪39‬‬
‫‪Synchronization Hardware‬‬
‫{)‪bool TestAndSet( bool &var‬‬
‫; ‪bool old_val = var‬‬
‫; ‪var = true‬‬
‫; ‪return old_val‬‬
‫}‬
‫המנעול מאותחל לערך‬
‫‪) false‬לא נעול(‬
‫{ )‪while (1‬‬
‫; ))‪while (TestAndSet(lock‬‬
‫קטע קריטי‬
‫; ‪lock = false‬‬
‫כל עוד מוחזר לי‬
‫שהמנעול כבר היה‬
‫נעול ‘אני ממתין‬
‫)‪(busy wait‬‬
‫קטע שיורי‬
‫{‬
‫הפתרון מבטיח בלבדיות‪ ,‬והתקדמות‪.‬‬
‫הוא אינו מבטיח הוגנות או מניעת הרעבה‬
‫©צבי מלמד‬
‫‪40‬‬
Synchronization Hardware
‫ אטומית‬swap() ‫שימוש בפקודת‬
:‫ אטומית‬swap ‫ מציעה פקודת‬,‫ בעל תכונות דומות‬,‫• פתרון דומה‬
void swap(bool &v1, bool &v2) {
bool temp = v1 ;
v1 = v2 ;
v2 = temp ;
}
41
‫©צבי מלמד‬
‫‪Synchronization Hardware‬‬
‫‪using atomic swap‬‬
‫{ )‪void swap(bool &v1, bool &v2‬‬
‫; ‪bool temp = v1‬‬
‫המנעול מאותחל לערך‬
‫‪) false‬לא נעול(‬
‫; ‪v1 = v2‬‬
‫; ‪v2 = temp‬‬
‫}‬
‫אני רוצה לנעול‬
‫– כלומר‪,‬‬
‫להציב ערך‬
‫‪ true‬למנעול‬
‫{ )‪while (1‬‬
‫; ‪key= true‬‬
‫)‪while (key == true‬‬
‫; )‪swap(key, lock‬‬
‫‪ swap‬מכניס ל‪ key -‬את ערכו‬
‫של ‪ ,lock‬לכן עת ערכו של ‪key‬‬
‫הוא ‪ false‬משמע 'תפסנו' את‬
‫המנעול פתוח‪ ,‬וה‪swap -‬‬
‫שהחזיר לנו איתות על כך‪ ,‬גם‬
‫נעל את המנעול עבורנו‪.‬‬
‫קטע קריטי‬
‫; ‪lock = false‬‬
‫קטע שיורי‬
‫{‬
‫©צבי מלמד‬
‫‪42‬‬
‫סינכרון בעזרת חומרה – ללא ‪starvation‬‬
‫•‬
‫הפקודות הנ"ל אינן מבטיחות חופש מהרעבה‪.‬‬
‫•‬
‫נציע פרוטוקול שישתמש ב‪ TestAndSet -‬ויממש את כל שלוש הדרישות‬
‫מפרוטוקול סינכרון‪.‬‬
‫•‬
‫מבני הנתונים המוכרים לכל התהליכים‪:‬‬
‫; } ‪bool waiting[n] = { false‬‬
‫•‬
‫מציין מי רוצה להיכנס לקטע הקריטי בעזרתו נעביר את הזכות להיכנס בין‬
‫המעוניינים בזה אחר זה‪ :‬כל מי שיצא יאתר את הבא אחריו שמעוניין להיכנס ו‪-‬‬
‫'יזמין' אותו‪.‬‬
‫; ‪bool lock = false‬‬
‫•‬
‫משתנה זה יציין האם אין אף תהליך שמעוניין להיכנס לקטע קריטי )ואז‬
‫הראשון שרוצה יכול להיכנס )ורק הקפד לנעול אחריך את הדלת(‬
‫•‬
‫לכל תהליך יוגדר משתנה לוקלי ‪ key‬כמו קודם‪.‬‬
‫©צבי מלמד‬
‫‪43‬‬
‫סינכרון בעזרת חומרה‬
‫ללא ‪ – starvation‬קטע הכניסה‬
‫{ )‪while (1‬‬
‫; ‪waiting[me] = true‬‬
‫; ‪key = true‬‬
‫"גם אני רוצה להכנס"‬
‫)‪while (waiting[me] && key‬‬
‫ישמש כמו במקרה הקודם‬
‫עם ‪TestAndSet‬‬
‫]‪ :waiting[me‬משתנה זה‬
‫עשוי לשנות את ערכו כאשר‬
‫'חבר' 'יזמין' אותי להיכנס‬
‫אחריו‪ ,‬כאשר הוא יצא‬
‫וישנה את ערך המשתנה‬
‫עבורי‬
‫‪ key‬ישנה את ערכו אם רק אני‬
‫רוצה להיכנס לקטע קריטי‬
‫ומתקיים ש‪lock == false -‬‬
‫; )‪key = TestAndSet(lock‬‬
‫; ‪waiting[me] = false‬‬
‫קטע קריטי‬
‫קטע היציאה‬
‫קטע שיורי‬
‫}‬
‫נראה שקיימת אפשרות להכנס‬
‫לקטע הקריטי אפילו אם המנעול‬
‫תפוס )‪(lock==true‬‬
‫האם זה ‪ BUG‬אם שזאת נקודה‬
‫מרכזית באלגוריתם?‬
‫©צבי מלמד‬
‫‪44‬‬
‫סינכרון בעזרת חומרה – ללא ‪starvation‬‬
‫קטע היציאה‬
‫כאשר אני יוצא אני בודק‬
‫אם יש מישהו שביקש‬
‫להיכנס לקטע הקריטי‬
‫‪...........‬‬
‫קטע קריטי‬
‫; ‪other = (me +1) % n‬‬
‫)]‪while (other != me &&!waiting[other‬‬
‫אם מצאתי מישהו כזה‬
‫; ‪other = (other +1) % n‬‬
‫)‪if (other != me‬‬
‫אני מסמן לו שיכנס‪...‬‬
‫;‪waiting[other] = false‬‬
‫‪else‬‬
‫; ‪lock = false‬‬
‫אחרת – אני משחרר את‬
‫המנעול‬
‫הקטע השיורי‬
‫}‬
‫הנקודה המרכזית‪ ...‬אני גואל את ‪ other‬מלולאת ההמתנה שהוא מצוי‬
‫בעיצומה‪.‬‬
‫הוא יוצא ממנה‪ ,‬מבלי שבעצם שוחרר המנעול! שיטת המיטה החמה‪.‬‬
‫©צבי מלמד‬
‫‪45‬‬
compare & swap
:‫ קיימת הפקודה הבאה‬IA32 -‫• ב‬
compare&swap(mem, R1, R2) {
if (mem == R1) {
mem = R2 ;
return true ;
}
return false ;
}
?TestAndSet ‫ כיצד נממש בעזרתה את‬:‫• תרגיל‬
46
‫©צבי מלמד‬
‫©צבי מלמד‬
‫‪47‬‬
‫סמפורים ‪Semaphores‬‬
‫•‬
‫•‬
‫•‬
‫•‬
‫•‬
‫סמפור = שיטת תקשורת המבוססת על שימוש בדגלים לאיתות‬
‫)לכל אות קיימת תנועת דגל מיוחדת(‪.‬‬
‫בהקשר שלנו‪ :‬התהליכים יאותתו זה לזה האם ניתן\אסור להיכנס‬
‫לקטע קריטי‬
‫מטרה‪ :‬הסמפור משמש בכדי להשיג מנעול‬
‫כיצד?‬
‫– סמפור הוא משתנה שלם )‪(int‬‬
‫– הניתן לאיתחול לערך ‪) 1‬או מספר גדול יותר(‬
‫בנוסף לאיתחול‪ ,‬מוגדרות עליו רק שתי הפעולות האטומיות‬
‫‪ (a‬המתנה\נעילה‬
‫‪ (b‬איתות\שחרור‬
‫©צבי מלמד‬
‫‪48‬‬
‫סמפורים ‪Semaphores‬‬
‫‪(a‬‬
‫המתנה\נעילה‬
‫‪(a‬‬
‫איתות\שחרור‬
‫{ )‪wait(s‬‬
‫)‪while (s <= 0‬‬
‫‪; // no-op‬‬
‫; ‪s--‬‬
‫}‬
‫{ )‪signal(s‬‬
‫; ‪s++‬‬
‫}‬
‫דרישות אטומיות‪:‬‬
‫‪s++, s-- .1‬‬
‫‪ .2‬כאשר הבדיקה ב‪ wait()-‬נכשלת אז חובה ש ‪ s--‬תהיה אטומית יחד עם בדיקת‬
‫התנאי שנכשלה‬
‫• אין מניעה שתהיינה פסיקות ב‪ busy-wait -‬של הלולאה ה‪while-‬‬
‫©צבי מלמד‬
‫‪49‬‬
‫סמפורים‬
‫• וכיצד ישמש אותנו הסמפור לצורך הגנה על קטע קריטי‬
‫• נגדיר משתנה שיוכר לכל התהליכים‪mutex = 1; :‬‬
‫• כל תהליך יבצע‪:‬‬
‫התהליך חסום כל עוד ערכו‬
‫של ‪ mutex‬איננו חיובי‪ .‬ברגע‬
‫שנכנסנו )ערכו היה חיובי(‬
‫הקטנו את ערכו ב‪1-‬‬
‫{ )‪while (1‬‬
‫;)‪wait(mutex‬‬
‫הקטע הקריטי‬
‫;)‪signal(mutex‬‬
‫שחרר את הסמפור לאחרים‪...‬‬
‫פקודת ‪ signal‬מגדילה את‬
‫ערכו ב‪1-‬‬
‫הקטע השיורי‬
‫}‬
‫©צבי מלמד‬
‫‪50‬‬
‫סמפורים‬
‫• סמפור יכול לסייע לנו גם במשימות סנכרון אחרות‪ ,‬לא רק בקטע‬
‫קריטי‬
‫• לדוגמה‪ :‬נניח ש‪ P2 -‬יכול לבצע קטע קוד רק אחרי ש‪' P1 -‬הכין' לו‬
‫נתונים‪ ,‬כלומר סיים לבצע קטע קוד‪.‬‬
‫איתחול‬
‫;‪synch = 0‬‬
‫‪ P2‬מבצע‬
‫‪ P1‬מבצע‬
‫קוד ההכנה‬
‫;)‪wait(synch‬‬
‫;)‪signal(synch‬‬
‫קוד השימוש‬
‫©צבי מלמד‬
‫‪51‬‬
‫מימוש סמפורים‬
‫• הפתרונות שראינו עד כה בין בתכנה ובין בחמרה חייבו ‪busy‬‬
‫‪) :waiting‬התהליך מבצע לולאה ריקה בקטע הכניסה שלו(‬
‫• אם הקטע הקריטי ארוך – ההמתנה מבזבזת הרבה ‪cpu-time‬‬
‫• סמפור עם המתנה עסוקה נקרא גם‪: spinlock :‬‬
‫‪.the process spins while waiting for a lock‬‬
‫• יתכן מצב שנעדיף המתנה עסוקה על‪-‬פני שתי החלפות הקשר –‬
‫לדוגמא‪ ,‬במערכת בת כמה מעבדים‪ ,‬עם קטע קריטי קצר יחסית‬
‫• במערכת עם מעבד יחיד‪ ,‬או כאשר הקטע‪-‬הקריטי ארוך – נעדיף‬
‫שהתהליך ילך לישון עד שהוא יוכל להיכנס לקטע הקריטי ואז‬
‫יעירו אותו‪.‬‬
‫©צבי מלמד‬
‫‪52‬‬
‫מימוש סמפורים‬
‫• נראה כיצד ניתן להשיג את התוצאה בעזרת סמפור "ישנוני"‪:‬‬
‫• נגדיר‪:‬‬
‫{ ‪struct Sleepy-Semaphore‬‬
‫; ‪int value‬‬
‫ערך הסמפור "כרגיל" ‪//‬‬
‫רשימת תהליכים ‪struct Process *waiting ; //‬‬
‫שממתינים על הסמפור ‪//‬‬
‫}‬
‫©צבי מלמד‬
‫‪53‬‬
5.2.12 ‫עד כאן‬
‫מימוש סמפורים‬
signal ‫פעולת‬
wait -‫פעולת ה‬
void signal(Sleepy-Semaphore s) {
void wait(Sleepy-Semaphore s) {
s.value++ ;
s.value-- ;
if (s.value <= 0) {
if (s.value < 0) {
remove a process from
add yourself to s.waiting
s.waiting and wake it up
go to sleep
}
}
}
}
‫ וגם‬,‫• מכיוון שכל אחת משתי הפעולות גם משנה ערך של משתנה‬
‫בודקת את ערכו אזי יש לדאוג שהן תבוצענה באופן אטומי‬
54
‫©צבי מלמד‬