لو شغلك Python فيه عمليات CPU-bound متوازية (معالجة صور، تشفير، parsing لـ payloads ضخمة)، الـ free-threaded build في Python 3.14 بيديك تسريع 3×–5× على جهاز بـ 8 cores. لو شغلك غالبه I/O (API calls، DB queries)، التغيير هيفرق معاك أقل من 5%، والـ single-thread هيبقى أبطأ 5–8%.
المشكلة باختصار
الـ GIL (Global Interpreter Lock) كان بيمنع threads متعددين من تنفيذ bytecode Python في نفس اللحظة. بقالنا سنين بنحايل المشكلة دي بـ multiprocessing، لكن الـ overhead بتاع الـ IPC وتكلفة الذاكرة عاليين جدًا لمهام قصيرة. في أكتوبر 2025، Python 3.14 عملت الـ free-threaded build (المعروف بـ python3.14t) خيار رسمي supported، مش experimental. وبحلول أبريل 2026، الـ ecosystem وصل لنقطة إن القرار بقى عملي، لكنه مش بديهي.
ليه القرار مش "بدّل على طول"
فيه تلات أسباب واقعية تخلّيك تفكر مرتين قبل ترحيل production workload كامل:
- single-thread أبطأ: الـ free-threaded build بيشيل optimizations معتمدة على الـ GIL، فالكود اللي مش parallel بيركض أبطأ 5–8% مقارنة بـ 3.13 الـ GIL'd.
- C extensions ناقصة دعم:
numpy≥ 2.2 وlxml≥ 5.3 مدعومين، لكن الـ long tail من مكتبات صغيرة لسه بتطلّع warnings أو بتقع لما تشتغل threaded. - debugging أصعب: race conditions في Python code بقت حقيقية. الكود اللي كان شغال صح لسنين بسبب الـ GIL ممكن يظهر bugs غريبة تحت load عالي.
متى الـ no-GIL يفرق فعلاً
التسريع الحقيقي بيظهر في الحالات دي بس:
- عمليات CPU-bound قصيرة (أقل من 100ms لكل task) — هنا
multiprocessingoverhead بيأكل المكسب. - تطبيقات بتعالج آلاف payloads متوازية في الذاكرة (JSON parsing، regex، hashing، compression).
- Data pipelines بتستخدم pandas/polars على DataFrames تدخل memory بالكامل.
- Web scrapers بيعملوا parsing محلي لـ HTML بعد ما يجيبوا البيانات.
السكربت: قياس فعلي على جهازك
قبل ما تقرر، قيس. السكربت ده بيشغّل hashing في threads متعددين ويطبع الزمن. شغّله مرة بـ python3.12 ومرة بـ python3.14t وقارن.
# bench_threads.py
import threading
import time
import hashlib
def hash_loop(iterations: int) -> None:
data = b"x" * 2048
for _ in range(iterations):
hashlib.sha256(data).hexdigest()
def run(threads_count: int, iterations_per_thread: int) -> float:
threads = [
threading.Thread(target=hash_loop, args=(iterations_per_thread,))
for _ in range(threads_count)
]
start = time.perf_counter()
for t in threads:
t.start()
for t in threads:
t.join()
return time.perf_counter() - start
if __name__ == "__main__":
total_work = 2_000_000
for n in (1, 2, 4, 8):
per_thread = total_work // n
elapsed = run(n, per_thread)
print(f"threads={n} elapsed={elapsed:.2f}s")