
הטקסט המלא של הדקות הראשונות בהרצאה «Threads - חלק 1» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.
אנחנו מגיעים לנושא מאוד נרחב ודי בסיסי שנקרא Threads. פה אנחנו הולכים לראות מה זה Thread, מה אפשר לעשות עם Thread ועוד מספר רב של דברים בנוגע ל-threading.
קודם כל, בואו נבין מה זה Thread. Thread זה בעצם תהליך נפרד של האפליקציה שאנחנו בונים מבחינת מעבד המחשב. בואו ניקח דוגמה. נניח שיש לנו איזושהי פונקציה, נקרא לה פרינט 1 ובוא נניח שהפונקציה שלנו מדפיסה 20 מספרים 20 ערכים ליתר דיוק בוא נניח שהיא מדפיסה את המספר 1 עם רווח אחריו 20 פעם
ובוא נניח שזאת לא הפונקציה היחידה שיש לנו יש לנו פה עוד פונקציה הפונקציה הבאה בתור תהיה פרינט 2 והיא מדפיסה 20 פעם את המספר 2 אם אנחנו קוראים לפרינט 1 ומיד אחר כך לפרינט 2 בצורה כזאת ברצף אז מה מה שאתם הולכים לראות זה כמובן 20 פעם את המספר 1 ואז 20 פעם את המספר 2. בואו נריץ.
ושניהם נמצאים אחד אחרי השני על המסך. אין שום בעיה עם ההדפסה הזו. שימו לב שזה קרה מאוד מהר. אם אנחנו מבצעים איזושהי פעולה, איזשהו תהליך שלוקח ממש ממש מהר, ברוב הגדול של המקרים אין לנו שום סיבה להתעסק עם הנושא הזה שנקרא "פרדים", כפי שציינתי, Thread זה בעצם איזשהו תהליך
נפרד של האפליקציה שלנו מבחינת ה-CPU של המחשב. CPU, Central Processing Unit, כלומר המעבד של המחשב. אנחנו יודעים שהמעבד של המחשב מבצע פקודה-פקודה בתוכנית שלנו.
אז מה זה אומר תהליך נפרד? לצורך ההדגמה, בואו ניקח את אותה הפקודה, אבל נבצע איזושהי המתנה בין הדפסה אחת להדפסה אחרת.
עשיתי פה איזושהי לולאה שלוקחת הרבה זמן בואו נריץ רגע את התוכנית ונראה כמה זמן זה לוקח עכשיו אנחנו רואים את האחדים מודפסים ורק אחרי שכל האחדים מסתיימים אז מתחילים השתיימים להיות מודפסים והדוגמה הזאת זו דוגמה שכבר לוקחת הרבה זמן יחסית מבחינת היוזר עכשיו כאשר אנחנו מדברים על פרדים
אנחנו מדברים על תהליכים נפרדים של האפליקציה מה זה אומר זה אומר שכרגע כל האפליקציה שלי, כל התוכנית שאנחנו מריצים, עובדת.
לפי הסדר, יש פה קריאה לפונקציה הראשונה, הפונקציה הזאת מתבצעת עד שהיא מסתיימת, וזה בעצם נקרא Synchronous Call. זה בעצם Blocking Operation. זה אומר שעד שהפונקציה הזאת לא מסתיימת, התוכנית שלנו עסוקה בפעילות של הפונקציה.
גם הפונקציה השנייה זה Synchronous Call. אם אנחנו נדפיס פה end of main אנחנו נראה שהend of main מוצג רק בסיום שתי הפונקציות. בכדי שזה ילך יותר מהר בואו ניקח כל פונקציה ונגיד שהיא רצה רק עשר פעמים, נריץ את זה, נראה שהפונקציה הראשונה בכלל תוקעת לנו את התוכנית, רק כשהיא מסתיימת מתחילה הפונקציה השנייה ואז היא תוקעת לנו את התוכנית
ורק כשהיא מסתיימת אז המיין ממשיך, מדפיס את הפקודה האחרונה ומסתיים. כאשר הדבר הזה לוקח הרבה זמן, אנחנו רוצים לשחרר את התוכנית מהתקיעות של הפונקציה.
איך עושים את זה? אנחנו רוצים לפתוח כביכול תהליך נוסף. כלומר, אנחנו רוצים שהפונקציה הראשונה תתבצע, ותוך כדי זה שהיא מתחילה להתבצע, גם הפונקציה השנייה תתחיל ביחד להתבצע.
איך זה יכול להיות? קיים לנו מעבד במחשב, והמעבד הזה מקבל לפי הסדר כל מיני תוכניות שרצות לציין. אצלנו במחשב כל תוכנית מקבלת את הזמן מעבד למשך זמן מאוד מאוד קצר ועובדת ספציפית על התוכנית הזאת כך מערכת הפעלה שהיא נחשבת מולטי טאסקינג כמו ווינדוס או כמו יוניקס לינוקס מק כל אלה מסוגלות לעשות הרבה דברים ביחד
כך מערכת הפעלה כזאת יכולה לשרת הרבה תוכנות כי מה שהיא עושה היא לוקחת תוכנית אחת נותנת לה זמן עבודה מאוד מאוד מאוד קצר הזמן הזה נקרא time slice, פיסת זמן, ואז משחררת אותה לעבוד, בעצם לחכות עד שהיא תקבל עוד פעם זמן CPU.
היוזר שמסתכל על התוכנית או שעובד עם המחשב לא יודע שכל תוכנית קיבלה זמן מאוד מאוד קצר, כי מבחינתו כולם מקבלות את תשומת הלב של המעבד.
אבל מה שקורה בפועל זה שכל תוכנית מקבלת פיסת זמן ומשתחררת וממתינה עד שהיא תקבל עוד פעם פיסת זמן. אז האנד יוזר מסתכל על כל המחשב ורואה שכולם עובדים, כל התוכנות במחשב עובדות כמו שצריך.
בדיוק כמו שתוכנית יכולה לקבל פיסת זמן, אנחנו יכולים שחלק מהתוכנית, לדוגמה פונקציה, תקבל גם היא פיסת זמן. ואת זה אנחנו עושים על ידי יצירת תהליך נפרד שמקבל את הפונקציה, התהליך הזה נותן למעבד של המחשב לעבוד על הפונקציה הזאת במקביל לכל התוכנית או לכל שאר הפונקציות בתוכנית או לשאר התוכניות האחרות שנמצאות במחשב.
אז התהליך הזה זה בעצם טרד נפרד שמתרחש. איך אנחנו עובדים עם טרד? דבר ראשון, מה שאנחנו צריכים לעשות זה לפתוח אובייקט מהמחלקה Thread. האובייקט מהמחלקה Thread אמור להפעיל את את הפונקציה שאנחנו רוצים שהוא יפעיל אותה כתהליך נפרד.
לכן אנחנו צריכים להציב על הפונקציה הזאת delegate. delegate אחד שקיים לנו נקרא thread start, נקרא לו ts1. אנחנו יכולים שה-thread start יצביע על print 1 ואז לתת add TS1 ל-thread ולהפעיל אותו.
בואו ניפטר רגע משתי הקריאות הסינכרוניות האלה ונבצע את אותו הדבר עבור טרד שני שמצביע על פרינט 2 ונתחיל אותו. עכשיו מה בעצם עשינו?
לקחנו דלגייט שמסוגל להצביע על פונקציה שהיא תמיד לא מקבלת כלום ולא מחזירה כלום, זה היה thread start, ונתנו אותו לאובייקט טרד והדבר הזה עדיין לא הפעיל לנו את הפונקציה.
אם אני כרגע אוריד את הקריאה של הסטארטים ואני אריץ, אתם הולכים לראות שהתוכנית מתחילה, מסתיימת ולא קרה שום דבר.
הפונקציות לא התחילו להתבצע. קריאה לפונקציית סטארט גורמת להרצת הפונקציה פרינט אחת במקרה הזה שהטרנד מצביע עליה אבל בצורה אסינכרונית ויהיה Non-Blocking Operation.
מה זה אומר בצורה אסינכרונית? זה אומר שמתבצעת קריאה ואז התוכנית ממשיכה לבצע דברים נוספים כי הפונקציה print1 הופעלה בתהליך נפרד לגמרי שרץ במקביל לשאר התהליכים והפעילויות של התוכנית.
בואו נריץ ונראה מה יקרה. אני מריץ. על ההתחלה אתם רואים ששתי הקריאות לפונקציות הסתיימו ואנחנו רואים פה את ה-end of main, אפילו אחת מהן התחילה לפניו, וכל הפעילות מתבצעת ביחד, כולל הפעילות של ה-end of main.
בואו רגע נמדוד זמנים. אני רוצה לקחת את הפעילות הקודמת, להוריד רגע את הקריאות האסינכרוניות, ולקחת רכיב שבודק זמנים. רכיב שבודק זמנים יכול להיות לדוגמה stopwatch.
מה שעשיתי כרגע זה הפעלתי את הסטופ וואץ' שעון עצר לפני הקריאות לשתי הפונקציות ואז ברגע שהן הסתיימו עצרתי אותו ובואו נסתכל על טוטל טיים ונקרא לאלאפסט מילי סקנד.