- بواسطة x32x01 ||
المشكلة منين أصلاً؟
لو عمرك سألت نفسك ليه Python بطيئة لما تيجي تعمل multithreading مقارنة بلغات زي Rust أو Go، فالسبب بيرجع لتاريخ اللغة.
البايثون اتعملت سنة 1991، وقتها مكنش فيه معالجات متعددة الأنوية (multi-core) زي دلوقتي، فقرروا يضيفوا حاجة اسمها GIL سنة 1992.
GIL (Global Interpreter Lock) هدفه كان يحمي اللغة من مشاكل threading ويخلي الكود سهل للـdebugging.
لكن دلوقتي مع تعدد الأنوية، نفس الـGIL بيحد من الاستفادة من قدرة المعالجات الحديثة على parallelism.
يعني بالمختصر، أي كود Python بيثبّت على GIL فـ multi-threading حقيقي مش بيحصل، وكل اللي موجود بس هو concurrency.
في لغات زي Rust، مفيش GIL، وبالتالي الكود بيعمل true multithreading، بس هنا بيظهر خطر اسمه Race Condition.
لو مش فاهم Race Condition، تخيل كده: اتنين توأم راحوا سحبوا فلوس من نفس الحساب في نفس التانية، السيستم ما لحقش يسجل العملية، وده بالظبط اللي بيحصل في الـRace Condition لو مفيش حماية زي الـmutex.
في البايثون، كل object عنده reference counter، وده بيخلي اللغة تعرف إمتى تقدر تعمل garbage collection بأمان بدون مشاكل Race Condition، وده بفضل وجود الـGIL.


وفي 3.14: بقت مدعومة رسميًا، وده بيخليك تشغل multi-core parallelism حقيقي بدون القيود القديمة.
الميزة هنا إنك ممكن تستخدم أكتر من Interpreter داخل process واحد، وكلهم يشاركوا البيانات، وده شبه اللي لغات زي Go عملته من زمان.
الكود دا بيحسب المربعات لكل رقم متوازيًا على الأنوية المتاحة بدل ما يشتغل serially.
في Python 3.14+: نفس الفكرة ممكنة باستخدام InterpreterPoolExecutor، وده بيسمح لك تتخطى GIL بطريقة أسهل من قبل.
لكن لسه في شوية تحديات:
الإصدارات الحديثة 3.13 و3.14 جابت حل قوي: إمكانية تشغيل أكتر من interpreter داخل process واحد، وبالتالي نقدر نستفيد من multi-core parallelism زي لغات حديثة زي Go وRust.
لو استخدمت Free-Threaded Python صح، هتحصل على أداء أقرب للـtrue parallelism، بس خد بالك من استهلاك الميموري ودعم المكتبات.
لو عمرك سألت نفسك ليه Python بطيئة لما تيجي تعمل multithreading مقارنة بلغات زي Rust أو Go، فالسبب بيرجع لتاريخ اللغة.
البايثون اتعملت سنة 1991، وقتها مكنش فيه معالجات متعددة الأنوية (multi-core) زي دلوقتي، فقرروا يضيفوا حاجة اسمها GIL سنة 1992.
GIL (Global Interpreter Lock) هدفه كان يحمي اللغة من مشاكل threading ويخلي الكود سهل للـdebugging.
لكن دلوقتي مع تعدد الأنوية، نفس الـGIL بيحد من الاستفادة من قدرة المعالجات الحديثة على parallelism.
يعني بالمختصر، أي كود Python بيثبّت على GIL فـ multi-threading حقيقي مش بيحصل، وكل اللي موجود بس هو concurrency.
إيه هو الـGIL وليه لغات تانية مش محتاجاه؟
الـGIL عبارة عن mutex كبير، يعني قفل بيسمح لثريد واحد بس إنه يتعامل مع الـPython interpreter في أي لحظة، والباقي يستنى دوره.في لغات زي Rust، مفيش GIL، وبالتالي الكود بيعمل true multithreading، بس هنا بيظهر خطر اسمه Race Condition.
لو مش فاهم Race Condition، تخيل كده: اتنين توأم راحوا سحبوا فلوس من نفس الحساب في نفس التانية، السيستم ما لحقش يسجل العملية، وده بالظبط اللي بيحصل في الـRace Condition لو مفيش حماية زي الـmutex.
في البايثون، كل object عنده reference counter، وده بيخلي اللغة تعرف إمتى تقدر تعمل garbage collection بأمان بدون مشاكل Race Condition، وده بفضل وجود الـGIL.
قبل كدا: إزاي الناس كانت بتتخطى المشكلة؟
قبل إصدار Python 3.14، الحل كان multiprocessing بدل threading. كل process ليه ميموري منفصل، وده بيخليك تستخدم ProcessPoolExecutor من مكتبة concurrent.futures، بس التعامل مع مشاركة البيانات كان معقد.التحسينات الجديدة في Python 3.13 و3.14
في إصدار 3.13: ظهرت فكرة Free-Threaded Python تجريبياً،وفي 3.14: بقت مدعومة رسميًا، وده بيخليك تشغل multi-core parallelism حقيقي بدون القيود القديمة.
الميزة هنا إنك ممكن تستخدم أكتر من Interpreter داخل process واحد، وكلهم يشاركوا البيانات، وده شبه اللي لغات زي Go عملته من زمان.
مثال كود عملي مع concurrent.futures (Python 3.14+)
Python:
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def task(n):
time.sleep(1)
return n * n
numbers = [1, 2, 3, 4, 5]
# Free-threaded Python: تقدر تشغل أكتر من Interpreter داخلي
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(task, numbers))
print("نتائج المهام:", results) إيه اللي لغات زي Go وRust بتعمله وPython لسه بتتعلمه؟
لغات زي Go نجحت في تشغيل أكتر من Interpreter داخل process واحد بشكل سلس، وده بيخلي:- parallelism حقيقي

- مشاركة البيانات بين threads أسهل

- استغلال كل الأنوية المتاحة

في Python 3.14+: نفس الفكرة ممكنة باستخدام InterpreterPoolExecutor، وده بيسمح لك تتخطى GIL بطريقة أسهل من قبل.
لكن لسه في شوية تحديات:
- كل interpreter بيستهلك ميموري زيادة

- دعم المكتبات الخارجية لسه محدود

- مشاركة البيانات بين interpreters لسه محدودة جداً

نصايح عملية للمطورين
- لو كودك بيحتاج CPU-bound tasks استخدم multi-processing أو النسخة free-threaded 3.14.
- لو كودك I/O-bound، الـasyncio أو ThreadPoolExecutor التقليدي كفاية.
- تابع المكتبات الداعمة لـ multi-core parallelism لتجنب مشاكل الأداء.

- اختبر الكود على عدد الأنوية المتاحة علشان تضمن استغلال أقصى قدر من المعالج.

الخلاصة
Python كانت بطيئة في threading بسبب GIL اللي موجود من التسعينات.الإصدارات الحديثة 3.13 و3.14 جابت حل قوي: إمكانية تشغيل أكتر من interpreter داخل process واحد، وبالتالي نقدر نستفيد من multi-core parallelism زي لغات حديثة زي Go وRust.
لو استخدمت Free-Threaded Python صح، هتحصل على أداء أقرب للـtrue parallelism، بس خد بالك من استهلاك الميموري ودعم المكتبات.