• בלוג
  • חדש בפייתון InterpreterPoolExecutor

חדש בפייתון InterpreterPoolExecutor

28/03/2026

המודול concurrent.futures קיים בפייתון עוד מגרסה 3.2 וכולל ממשק אחיד לביצוע פעולות במקביל. הנה דוגמה קצרה שסופרת מספרים ראשוניים בצורה מקבילית באמצעות Executor, תחילה עם תהליכונים ולאחר מכן עם תהליכים מלאים:

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor
import math
import time

def is_prime(n: int):
    for i in range(2, math.floor(math.sqrt(n)) + 1):
        if n % i == 0:
            return False

    return True

if __name__ == "__main__":
    t0 = time.time()
    with ThreadPoolExecutor(max_workers=4) as p:
        print(sum(p.map(is_prime, range(1_000_000))))
    t1 = time.time()

    with ProcessPoolExecutor(max_workers=4) as p:
        print(sum(p.map(is_prime, range(1_000_000))))
    t2 = time.time()

    print(f"Thread count took {t1-t0}")
    print(f"Process count took {t2 - t1}")

התוצאה כצפוי היא חישוב הרבה יותר מהיר באמצעות threads בגלל שהסינכרון הרבה יותר מהיר וגם יצירת threads הרבה יותר מהירה מיצירת Processes. אבל לשימוש ב threads יש שני חסרונות:

  1. אין הפרדה בין האוביקטים - כלומר קוד מכל threads יכול לגשת ולעדכן אוביקטים גלובאליים.

  2. גרסאות רבות של פייתון מגיעות עם Global Interpreter Lock מה שאומר שכל פעולה של פייתון בתוך ה thread צריכה לנעול את אותו מנעול גלובאלי.

גרסה 3.14 של פייתון הוסיפה פתרון ביניים שנקרא InterpreterPoolExecutor. מבצע זה יריץ כל משימה במפרש פייתון עצמאי שרץ ב thread נפרד, כלומר במקום להפעיל fork ולייצר תהליך מערכת הפעלה חדשה למפרש החדש הם מריצים את המפרש באותו תהליך. התוצאה היא הפרדה מלאה בין משתנים כי לכל מפרש יש את המשתנים הגלובאליים שלו, הפרדה בנעילות כי לכל מפרש יש את ה GIL שלו ועדיין ריצה יותר מהירה כי אנחנו רצים ב threads במקום ב processes. כמה יותר מהיר? זה מה שלקח לספור מספרים ראשוניים אצלי על המק:

Thread count took 6.0312182903289795
Process count took 80.60068678855896
Interpreter count took 24.76494789123535

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