Thread Pool בשפת #C הוא מעין מאגר שמחזיק בתוכו Threads שניתן לשלוף ולאחסן בהתאם לצרכי התוכנית שלנו ובכך לחסוך במשאבי זיכרון. בפרק זה של המדריך נדגים כיצד ניתן לעשות זאת.
בפרקים הקודמים של המדריך למדנו על נעילות אקסקלוסיביות ונעילות שאינן אקסקלוסיביות,
אך חישבו על המצב הבא – יש ברשותנו תוכנית שב-Thread הראשי שלה נוצרים ומנוהלים Threads רבים.
במקרה כזה, יכול להיווצר מצב שבו-זמנית מנוהלים מופעים רבים של Thread – מה שיכול לגרום להאטה בתוכנית שלנו.
במאמר זה נראה כיצד ביכולתנו לנהל מצבים כאלו ביעילות באמצעות Thread Pooling.
אורך החיים של מופע ה-Thread
ראשית עלינו להבין את מחזור החיים של מופע ה-Thread.
שימו לב לתרשים הזרימה הבא:
כלומר שאם נרצה לתאר זאת במילים נוכל לכתוב כך:
- נשלחת בקשה מסוימת בתוכנית שלנו
- נוצר Thread חדש שיצרנו על מנת לנהל בקשה זו
- ה-Thread יאסוף את המשאבים שאיתם הוא הולך לעבוד
- הבקשה תטופל על ידי ה-Thread
- ה-Thread "יסולק" מהזיכרון על ידי אוסף הזבל
צעדים אלו יחזרו על עצמם שוב ושוב עבור כל בקשה שנשלחת בתוכנית שמשתמשת ב-Threads רבים.
כלומר שבכל פעם שאנו יוצרים מופע של Thread, מוגדרת עבורו כתובת בזיכרון,
מה שגם אומר שהוא "תופס" מקום מסוים בזיכרון (הוקצו עבורו משאבי זיכרון).
אם כך, חישבו על מצב שבו נוצרים Threads רבים בו-זמנית בתוכנית שלנו –
מצב כזה יכול לפגוע בביצועי התוכנית ולגרום להאטה בתוכנית.
פתרון יעיל לדוגמא הוא למשל שבשלב האחרון של מחזור החיים של מופע ה-Thread,
במקום לתת לאוסף הזבל "לסלק" את מופע ה-Thread, נאכסן אותו ב-Thread Pool.
במקרה כזה כל מופע מאותחל פעם אחת ויקרא להיכנס לפעולה כשתישלח בקשה,
ובכך למעשה נמנע יצירה של עוד ועוד אובייקטים מאותו ה-Thread בו-זמנית.
Thread Pool
Thread Pool הוא למעשה מעין מאגר שמחזיק בתוכו Threads שניתן לשלוף ולאחסן בהתאם לצרכים שלנו.
כאשר אנו משתמשים בו, מחזור החיים של מופע ה-Thread ישתנה.
נוכל בתום הטיפול בבקשה להעביר את מופע ה-Thread למאגר,
ובכך אוסף הזבל לא ינקה אותו בתום השימוש:
שימו לב כי מעתה בכל פעם שתישלח בקשה לאותו ה-Thread,
לא נצטרך לעבור את כל התהליך הזה מחדש.
כעת נוכל פשוט לשלוף ולאכסן אותו ב-Thread Pool:
מחלקת ThreadPool
על מנת שנוכל לבצע Thread Pooling נשתמש במתודה הסטטית QueueUserWorkItem:
ThreadPool.QueueUserWorkItem(new WaitCallback(MyMethod));
- כתוצאה מכך שאנו מייבאים את System.Threading – נוכל להשתמש במחלקת ThreadPool.
- כפרמטר מתודה זו מקבלת אובייקט WaitCallback.
- כאשר אנו יוצרים אובייקט WaitCallback נשלח לו כפרמטר את המתודה הרצויה
- מדובר בדלגאט, ובמקרה הזה הדלגאט מצפה לקבל מתודה שמקבלת Object כפרמטר, אחרת נקבל שגיאה.
שימו לב ל-Code Snippet הבא אשר יבאר נושא זה:
using System.Threading; namespace ThreadPooling { public class Program { public static void Main() { for (int i = 0; i < 10; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(MyMethod)); } Console.Read(); } public static void MyMethod(object obj) { Thread thread = Thread.CurrentThread; string message = $"Is Background Thread: {thread.IsBackground}, Is ThreadPool Thread: {thread.IsThreadPoolThread}, Thread ID: {thread.ManagedThreadId}"; Console.WriteLine(message); } } }
בתוכנית זו למעשה השתמשנו ב-Thread Pooling על מנת להמחיש את אופן הפעולה שדובר לפני,
במקום שכל פעם ניצור מופע של Thread מסוים, שלפנו והחזרנו את ה-Thread בכל פעם אל ה-Thread Pool.
*אם נרצה למשל לסיים באופן מיידי את עבודתו של Thread כלשהו, נוכל להשתמש במתודה Abort.
לסיכום ניתן לומר כי Multithreading הוא נושא עמוק וחשוב מאין כמוהו,
אשר באמצעות שימוש בו נוכל לנהל ולסנכרן את התוכנית שלנו בצורה יעילה.
לקריאה מורחבת על Thread ו-Threading באתר של מייקרוסופט יש ללחוץ כאן.