מודלים של מקביליות
בתוכנה מודרנית יש לעיתים קרובות צורך לבצע משימות רבות בו-זמנית או לנהל הרבה פעולות באופן יעיל.
פייתון מספקת מספר מודלים של מקביליות:
- threading – חוטים (threads)
- multiprocessing – תהליכים נפרדים
- asyncio – תכנות אסינכרוני מבוסס אירועים (event loop)
לכל אחד מהם יתרונות, חסרונות ושימושים מתאימים.
GIL – נעילת המפרש הגלובלית
ב־CPython, ה־GIL מונע מהרצת מספר חוטים (threads) בו-זמנית על קוד פייתון.
זה מפשט את ניהול הזיכרון – אך מהווה צוואר בקבוק לתוכניות שמעמיסות על המעבד (CPU-bound).
התוצאה היא ש־threads לא יכולים לפעול במקביל אמיתי על כמה ליבות מעבד כאשר מדובר בקוד פייתון טהור.
הרצה מקבילית מבוססת תהליכים (Process-Based Parallelism)
כדי לנצל באמת את כל ליבות המעבד עבור משימות כבדות, יש להשתמש ב־multiprocessing.
מודול זה מאפשר יצירת תהליכים נפרדים – כל תהליך מריץ מפרש פייתון נפרד עם זיכרון משלו.
לכן, אין GIL משותף, ומשימות כבדות למעבד יכולות לרוץ באמת במקביל.
הממשק של multiprocessing דומה מאוד לזה של threading, עם מחלקות כמו:
- Process – בדומה ל־Thread
- Pool
- concurrent.futures.ProcessPoolExecutor – ממשק פשוט יותר
דוגמה לשימוש ב־multiprocessing
נקודות חשובות לגבי multiprocessing
- ב-Windows (ובמערכות נוספות) תהליך חדש נוצר ע"י ייבוא המודול הראשי מחדש – לכן חובה להשתמש ב־if __name__ == "__main__" כדי למנוע יצירה רקורסיבית של תהליכים.
- הנתונים בין תהליכים מבודדים – כדי לשתף מצב צריך להשתמש ב־IPC כמו Queue, Pipe, או multiprocessing.Value, Array.
פתיחת תהליך יקרה יותר מ־thread – לכן מתאימה למשימות כבדות שדורשות מקביליות אמיתית.
- קיימת גרסה בשם multiprocessing.dummy – זו עטיפה סביב threading לצורך תאימות ממשק.
- ניתן גם להשתמש בספריות גבוהות יותר כמו joblib או pathos שמספקות ממשק נוח למקביליות.
שילוב נכון בין Threads, Async ו־Processes בהתאם לאופי המשימה – הוא המפתח לכתיבה יעילה של תוכנה מקבילית בפייתון.
שימוש ב־ProcessPoolExecutor מתוך concurrent.futures:
שימוש זה מפעיל בריכה של תהליכים (worker processes) ואוסף את התוצאות מאובייקטי Future.
המחלקה ProcessPoolExecutor דואגת בעצמה להקמה ולסיום של הבריכה.
מומלץ להשתמש במקביליות מבוססת תהליכים (processes) עבור משימות כבדות למעבד (CPU-bound) – כמו חישובים מתמטיים כבדים, עיבוד תמונה, וכו', שבהן ה־GIL מגביל את השימוש ב־threads.
בנוסף, אפשר גם לשקול ספריות שמרפות את ה־GIL, כמו NumPy, אשר מבצעת חישובים פנימיים בקוד C מרובה־חוטים.