PracticU

סמפור ונעילות - Locks and Semaphores

בלי כתוביותעם כתוביות
PracticU › קורס פיתוח אפליקציות - לאייפון iPhone iOS - ב-Swift-Xcode › סמפור ונעילות - Locks and Semaphores
מילון מושגים:
משתנה - שם שמפנה למקום בזיכרון שבו נשמר ערך שיכול להשתנות במהלך ריצת התוכנית, כמו מספר, טקסט או אובייקט.
לולאה - מבנה בקרה שחוזר על קטע קוד שוב ושוב כל עוד תנאי מתקיים או עבור כל פריט באוסף.
אסינכרוני - אופן פעולה שבו פעולה ארוכה רצה ברקע והתוכנית ממשיכה בלי להמתין לה, ומטופלת כשתסתיים.
תהליכון - יחידת ביצוע בתוך תהליך, המאפשרת להריץ כמה מהלכים במקביל ולחלוק את אותו זיכרון.
שרת - מחשב או תוכנה שמספקים שירות למחשבים אחרים (לקוחות) ברשת, למשל אחסון קבצים, אתר אינטרנט או מסד נתונים, ומגיבים לבקשות שלהם.
ממשק - נקודת מגע בין רכיבים: ממשק משתמש שבו אדם מפעיל תוכנה, או ממשק תכנותי שמגדיר אילו פעולות רכיב אחד חושף לאחר.

סמפור ונעילות - Locks and Semaphores
כשכמה threads כותבים לאותו משתנה, התוצאות יוצאות לא צפויות. השיעור מראה בעיה כזו, שני פתרונות, ואיך מגבילים כמה threads עובדים במקביל ואיך מחכים לסיום קבוצת משימות.
הבעיה
בקלאס ShipHerd יש counter ופעולה countSheepAndPrint שמגדילה אותו ב-1 ומדפיסה. בלולאה רגילה (סינכרונית) הספירה תקינה. כשקוראים לפעולה מתוך q.async בתור גלובלי, כמה threads קוראים ומשנים את הערך יחד, והפלט נראה כמו 6, 6, 6, 7, 5, 7, ומשתנה בין ריצות.
השלבים העיקריים
1. נעילה עם NSLock: קוראים ל-lock() בתחילת הפעולה ול-unlock() בסופה, וכך כל פעם נכנס thread אחד, והספירה תקינה.
2. defer: אם שוכחים unlock, כל התוכנית תקועה במצב deadlock. הכלי של Swift הוא defer { lock.unlock() } מיד אחרי ה-lock, והקוד רץ בסיום הפעולה בכל מסלול.
3. פתרון חלופי עם תור טורי: תור DispatchQueue(label:) שמריץ את counter += 1 וההדפסה, אחד אחרי השני.
4. הגבלת מספר עובדים עם DispatchSemaphore: סמפור הוא מנעול עם מונה. יוצרים אותו עם הערך 2, וכל משימה קוראת ל-wait() בתחילתה ול-signal() בסופה (גם כאן עם defer). בדמו של הורדת תמונות רצות רק שתי משימות בו זמנית.
5. DispatchGroup: יוצרים group, מעבירים אותו לפעולות כמו q.async(group: group), ובסוף group.notify(queue: .main) מפעיל קוד כשכל המשימות בקבוצה הסתיימו. הדוגמה מדפיסה "all jobs are done". הסדר בין המשימות אינו מובטח, ואין צורך לספור.
מתי להשתמש
- נועלים פעולות שכותבות למידע משותף, כמו כתיבה לקובץ או שינוי מונה.
- מגבילים מספר threads כשאין טעם להעמיס על שרת, למשל לא יותר מארבע הורדות בו זמנית.
- משתמשים בקבוצה כשרוצים לעדכן את הממשק רק אחרי שכל ההורדות הסתיימו, למשל מעבר ממסך פתיחה למסך הבית.
אחרי השיעור אפשר להגן על מידע משותף מפני race conditions, להגביל concurrency ולהמתין לסיום כמה משימות.
השיעור הבא בקורס: שרת HTTP שליחת פרמטרים ב-Swift-Xcode

פרקי ההרצאה: סמפור ונעילות - Locks and Semaphores

  1. 00:00

    בעיות נפוצות בעבודה עם טרדים

  2. 01:19

    מחלקת SheepHerd עם קאונטר

  3. 03:15

    ספירה סינכרונית תקינה

  4. 04:07

    הרצה אסינכרונית ותוצאות שגויות

  5. 05:06

    הסבר על Race Condition

  6. 06:23

    סנכרון בעזרת NSLock

  7. 07:40

    Deadlock והשימוש ב-defer

  8. 08:59

    פתרון חלופי עם Serial Queue

תמלול ההרצאה: סמפור ונעילות - Locks and Semaphores

הטקסט המלא של הדקות הראשונות בהרצאה «סמפור ונעילות - Locks and Semaphores» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.

  1. 00:02

    שלום וברוכים הבאים. בשיעורים הקודמים ראינו עבודה עם GCD ולמעשה עבודה עם טרדים שנותנת לנו אפשרות להריץ משימות ארוכות ברקע כמו כן ראינו איך להחזיר את התשובה אחר כך על ה-UI טרד וגם ראינו איך אפשר לכתוב את הכל במחלקה או בסטרקצ'ר נפרד ולקבל בסופו של דבר תשובה היום אנחנו רוצים לדבר על התמודדות עם בעיות מקובלות או בעיות שחוזרות על עצמן הרבה בעבודה

  2. 00:28

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

  3. 00:39

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

  4. 00:57

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

  5. 01:08

    אז אנחנו קופצים ל-Xcode ומייצרים לעצמנו פרויקט חדש. אנחנו נקרא לפרויקט הזה לוקס סמא פורס דמו ונתקדם הלאה.

  6. 01:29

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

  7. 01:45

    עכשיו בעדר הזה של הכבשים שלנו אנחנו רוצים לעשות משימה נורא נורא פשוטה אני מייצר פה קלאס או סטרקצ'ר זה לא משנה בוא נקרא לו שיפ הרד ונגדיר לנו קאונטר והקאונטר הזה שווה לאפס אוקיי עכשיו מה היה קורה אילו היינו פשוט מייצרים פה איזושהי פעולה שמגדילה את הקאונטר נגיד אנחנו רוצים לספור כבשים אז בוא נאמר count ship and print

  8. 02:12

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

  9. 02:36

    אנחנו נפתח פה את ה-view controller בצד אני לחצתי על אופצ'ן וה-view קונטרולר פה עם קליק כדי לראות את זה בצד ימין למקרה שמעניין אתכם מה עשיתי ועכשיו אוקיי יש לנו שיפ הרד אז לד הרד שווה שיפ הרד חדש אוקיי ובוא נפעיל את השיפ הרד הזה נכון? זאת אומרת בוא נעשה פה איזה שילולה for i in 0 עד 10 בוא נספור כמה כבשים בוא

  10. 03:08

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

  11. 03:27

    אוקיי, אם אני אריץ עכשיו את הדוגמה, אתם לא תראו שום בעיה, בגלל שזה תכנות סינכרוני, זה לא תכנות אסינכרוני, נכון? זאת אומרת, זו תכנות רגילה לגמרי, הקומפיילר פה מתלונן שלא השתמשתי בערך של i, אבל אנחנו יכולים לראות טוב מאוד שוואלה, 1 עד 11 הכבשים שלי סופרות, הסיבה זה שרצנו פה מ-0 עד 10 כולל, נכון?

  12. 03:47

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

  13. 04:06

    זה תכנות סינכרוני. איך אנחנו נעשה את זה תכנות אסינכרוני? תכנות אסינכרוני אנחנו נייצר לעצמנו dispatch queue נכון? אז בוא נעשה let q שווה dispatch queue global ואז אם אנחנו ניתן עכשיו את המשימות האלה לטרדים אנחנו פשוט נגיד q.async ופשוט נקרא לפעולה

  14. 04:25

    מלמעלה נכון? herd.countsheet אוקיי וזה תכנות כבר אסינכרוני אז בוא נראה אם יש פה בעיות בתכנות הזה נראה אתם יכולים לראות את התוצאות בעצמכם נכון? זה קצת מוזר 666757 ככה לא נראה עם הספירת כבשים כמו שאנחנו מדמיינים אותה נכון?

  15. 04:55

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

  16. 05:07

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

  17. 05:28

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

  18. 05:43

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

  19. 05:57

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

  20. 06:08

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

  21. 06:27

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

  22. 06:47

    עכשיו מנעול זה בדומה למה שיש נגיד, אנחנו צריכים לכתוב פה var let, נכון? עכשיו מנעול זה בדומה למה שיש בתחנות דלג כשאתם באים לקחת את המפתח של השירותים, אוקיי?

  23. 07:00

    יש לכם את המפתח ורק אתם יכולים להיכנס עם המפתח. ברגע שתחזירו את המפתח מישהו אחר יוכל להיכנס לשירותים. איך אנחנו עושים את זה בתכנות?

  24. 07:09

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

  25. 07:29

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

  26. 07:45

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

  27. 07:58

    והיי, כל התוכנית שלנו עכשיו תקועה. בסדר? יש לנו פה מצב של deadlock, כל הטרדים מחכים ואף פעם הם לא יוכלו להשתחרר. אופס.

  28. 08:09

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

  29. 08:21

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

  30. 08:42

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

  31. 08:52

    נועלים ובסופו של המתודה משחררים את המנעול וכדי לא לשכוח לעשות את זה בסוף המתודה אפשר לעשות את זה בקוד של דיפר מעולה אז ככה אפשר לעשות את זה בעבודה עם מנעולים עכשיו דרך אגב את אותו פתרון לבעיה הזאת הייתי יכול לעשות בדרך אחרת בוא נעשה את זה בעוד דרך כדי שיהיה לכם בראש גם את זה אני אעשה version 2 לקלאס שלנו אוקיי אני אעשה ship herd v2 כאן אני ארצה פשוט לעשות את אותו דבר

  32. 09:24

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

  33. 09:48

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

  34. 10:01

    אני אתיק מפה את השם של הפעולה כדי ככה לקצר את העניין ובואו נכתוב אותה כאן אוקיי עכשיו בפעולה שלנו אנחנו פשוט רוצים לשלוח את העבודה ל-Q וה-Q מריץ את הפעולות אסינכרונית אבל אחת אחרי השנייה לפי הסדר אז אוקיי self.counter +=1 ו-print self.counter עכשיו למה אני כותב פה self כל הזמן בגלל שאנחנו נמצאים בבלוק

  35. 10:32

    נכון בקלוז'ר ובקלוז'ר אם אני לא אכתוב בהתחלה self אז הוא יזהיר אותי שיש פה רפרנס לסלף מתוך קלוז'ר אוקיי אז ככה מנגנון אזהרה בשבילנו בוא נבדוק שה-Q החדש שלנו עובד באותה מידה אז אנחנו נקרא פה הפעם ל-V2 ונריץ את אותה פונקציה בסדר נריץ את אותה דוגמה פה אבל עם הגרסה החדשה שכתבנו ואנחנו מריצים את זה ובוא נבדוק בוא נראה

  36. 11:08

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

  37. 11:22

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

  38. 11:42

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

  39. 11:58

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

  40. 12:16

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

  41. 12:35

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

לקורס המלא: קורס פיתוח אפליקציות לאייפון

ההרצאה הקודמת: עבודה עם Callbacks עבור משימות אסינכרוניות

ההרצאה הבאה: שרת HTTP שליחת פרמטרים ב-Swift-Xcode

קורסים נוספים במסלול

  • קורס יסודות התכנות - שפת JAVA - קורס ג'אווה למתחילים
  • קורס פיתוח אפליקציות - לאנדרואיד - Android - ב-JAVA
  • קורס פלאטר - Flutter - שפת DART של גוגל למובייל - פיתוח אפליקציות לאנדרואיד ואייפון
  • קורס PhoneGap - פיתוח אפליקציות לאנדרואיד ואייפון