לדלג לתוכן

תהליכונים בלינוקס מוסברים - מתי לפצל לתהליך חדש ומתי מספיק תהליכון

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

אז מה ההבדל בין תהליך לתהליכון

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

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

איך בלינוקס תהליך ותהליכון בעצם נבנים מאותה קריאה

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

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

למה בכלל להשתמש בתהליכונים

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

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

המחיר של שיתוף - תנאי מרוץ

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

מתי בוחרים תהליך ומתי תהליכון

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

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

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

הצטרפו לקהילה בדיסקורד

לסיכום

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