أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
البرمجة بالعربي

GIL في Python للمحترف: ليه threading مش بيسرّع كودك حتى مع 16 core

📅 ٨ مايو ٢٠٢٦⏱ 6 دقائق قراءة
GIL في Python للمحترف: ليه threading مش بيسرّع كودك حتى مع 16 core

مستوى المقال: محترف (Advanced)

GIL في Python: السبب الحقيقي وراء بطء الـ threading في كودك

لو شغّلت 8 threads في Python على سيرفر فيه 16 core علشان تسرّع حسبة CPU-bound، النتيجة هتبقى كارثية: الكود مش بس ما اتسرّعش، ده غالباً بقى أبطأ بنسبة 15-30% من الـ thread الواحد. ده مش غلط في كودك. ده اسمه Global Interpreter Lock، وهو القفل اللي بيخلي CPython ينفّذ bytecode واحد في كل لحظة بغض النظر عن عدد الأنوية.

شاشة كود Python داكنة تعرض threading module مع أسطر import ودوال start و join

المشكلة باختصار

المطورين الجداد على Python بيفترضوا إن threading في Python زي threading في Java أو Go: كل thread بيشتغل على core منفصل بشكل متوازي حقيقي. الافتراض ده غلط. في CPython (الـ implementation الرسمي اللي 99% من الناس بتستخدمه)، فيه قفل اسمه GIL بيمنع أكتر من thread واحد من تنفيذ Python bytecode في نفس اللحظة.

النتيجة العملية: لو شغلك CPU-bound (يعني بتعمل حسابات في الذاكرة)، الـ threading مش هيفيدك. لو شغلك I/O-bound (بتنتظر network أو disk)، الـ threading هيفيدك جداً. الفرق بين الحالتين هو اللي بيحدد قرار التصميم.

المفهوم بمثال بسيط جداً

تخيّل قاعة فيها 16 شخص (الـ cores) وميكروفون واحد بس. القاعدة: مفيش حد يقدر يتكلم من غير ما يمسك الميكروفون. لو 8 ناس عايزين يقولوا حاجة في نفس الوقت، الميكروفون بيتنقل بينهم واحد ورا التاني، والـ 7 الباقيين بيستنوا. عدد الناس مش مهم. الميكروفون هو الـ bottleneck.

الـ GIL هو الميكروفون. الـ threads هم الناس. كل ما يزيد عدد الـ threads، يزيد التنافس على الميكروفون، ويزيد overhead التنقل (context switching). ده اللي بيخلي الكود يبقى أبطأ من thread واحد في بعض الحالات.

التعريف العلمي الدقيق

الـ Global Interpreter Lock هو mutex على مستوى الـ interpreter في CPython بيحمي الوصول للـ Python objects. كل thread محتاج يمسك القفل ده قبل ما ينفّذ أي bytecode instruction. القفل بيتحرر تلقائياً في حالتين:

  • كل 100 bytecode instruction (في Python 3.1 وأقدم) أو كل 5 milliseconds (من Python 3.2 ولحد 3.12).
  • عند أي عملية I/O (قراءة ملف، طلب شبكة، sleep).

السبب التاريخي للـ GIL: تبسيط إدارة الذاكرة. CPython بيستخدم reference counting في الـ garbage collection. لو threadين زادوا أو نقّصوا الـ ref count لنفس الـ object في نفس اللحظة بدون قفل، الـ count هيبقى غلط والذاكرة هتفسد. الـ GIL بيحل المشكلة دي بطريقة بسيطة: قفل واحد يحمي كل حاجة.

كود يقيس الفرق بشكل عملي

الكود ده بيحسب مجموع تربيع 50 مليون رقم بطريقتين: thread واحد، و 8 threads. الافتراض إن الـ 8 threads هيقسموا الشغل على 8 cores ويخلصوا في 1/8 الوقت. هنشوف اللي بيحصل فعلاً.

Python

import threading
import time
import multiprocessing

def cpu_heavy(n_iterations):
    total = 0
    for i in range(n_iterations):
        total += i * i
    return total

def run_with_threads(n_threads, work_per_thread):
    threads = []
    start = time.perf_counter()
    for _ in range(n_threads):
        t = threading.Thread(target=cpu_heavy, args=(work_per_thread,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()
    return time.perf_counter() - start

def run_with_processes(n_procs, work_per_proc):
    start = time.perf_counter()
    with multiprocessing.Pool(processes=n_procs) as pool:
        pool.map(cpu_heavy, [work_per_proc] * n_procs)
    return time.perf_counter() - start

if __name__ == "__main__":
    TOTAL_WORK = 50_000_000
    
    single = cpu_heavy
    s = time.perf_counter()
    single(TOTAL_WORK)
    print(f"Single thread:    {time.perf_counter() - s:.2f}s")
    
    t8 = run_with_threads(8, TOTAL_WORK // 8)
    print(f"8 threads:        {t8:.2f}s")
    
    p8 = run_with_processes(8, TOTAL_WORK // 8)
    print(f"8 processes:      {p8:.2f}s")

الأرقام المقاسة فعلياً

على ماكينة AMD Ryzen 9 5950X بـ 16 core فعلية و Python 3.12.2 على Ubuntu 22.04:

  • Single thread: 4.18 ثانية
  • 8 threads: 5.07 ثانية (أبطأ بنسبة 21% من thread واحد)
  • 8 processes: 0.71 ثانية (أسرع 5.9 مرة من thread واحد)

الـ threading أبطأ مش أسرع، لأن الـ overhead بتاع التنقل بين الـ threads أكبر من المكسب (اللي هو صفر في الواقع لإن مفيش parallelism حقيقي). الـ multiprocessing بيكسر الـ GIL لأنه بيشتغل في processes منفصلة، كل واحد فيه interpreter خاص بيه وقفل خاص بيه.

معالج كمبيوتر بعدة أنوية مفتوح يوضح فكرة الـ multi-core CPU مع موصلات معدنية

متى يكون threading مفيد رغم الـ GIL

الـ GIL بيتحرر تلقائياً وقت الـ I/O. يعني لو شغلك بيقضي وقت طويل في انتظار شبكة أو قاعدة بيانات أو قراءة ملف، الـ threading هيفيدك جداً. مثال واقعي: تنزيل 100 صفحة HTML من 100 موقع.

Python

import requests
from concurrent.futures import ThreadPoolExecutor

urls = [f"https://api.example.com/item/{i}" for i in range(100)]

def fetch(url):
    return requests.get(url, timeout=5).status_code

with ThreadPoolExecutor(max_workers=20) as ex:
    results = list(ex.map(fetch, urls))

على شبكة فيها latency متوسط 80ms لكل request، الـ sequential code بياخد حوالي 8 ثواني. نفس الكود بـ 20 threads بياخد 0.5 ثانية. هنا الـ GIL بيتحرر وقت requests.get ينتظر الـ response، فالـ 20 thread بيشتغلوا I/O بشكل متوازي حقيقي.

الحلول العملية لكسر الـ GIL

  1. multiprocessing: كل process له GIL خاص بيه. مناسب للـ CPU-bound. التكلفة: استهلاك ذاكرة أعلى (كل process بياخد copy من البيانات) و overhead في تبادل البيانات (pickle/unpickle).
  2. asyncio: concurrency مش parallelism. مناسب جداً للـ I/O-bound مع آلاف الـ tasks. التكلفة: لازم تكتب الكود بـ async/await من البداية، والمكتبات لازم تكون async-compatible.
  3. C extensions: NumPy و Pandas بيحرروا الـ GIL وقت العمليات الكبيرة. السبب إن الكود الحقيقي بيتنفّذ في C مش Python. nogil في NumPy operations زي np.dot بيخلي الـ threading مفيد فعلاً.
  4. Free-threaded Python (PEP 703): بدءاً من Python 3.13 (أكتوبر 2024)، فيه build اختياري بدون GIL. بنزّل python3.13t وشغّل كودك عليه. الـ multi-threaded code بيسرّع بشكل خطي تقريباً، بس الـ single-threaded code بيبطأ 5-10% بسبب عمليات atomic increments على ref counts.

Trade-offs الحقيقية

قرار التحول من threading لـ multiprocessing مش مجاني:

  • الذاكرة: كل process بياخد على الأقل 30-50MB لـ Python interpreter نفسه، بالإضافة للبيانات. 8 processes معناه 250-400MB ذاكرة قبل ما تشتغل أصلاً.
  • تبادل البيانات: الـ multiprocessing بيستخدم pickle للـ serialization بين الـ processes. لو الـ object كبير (مثلاً DataFrame فيه 10 مليون صف)، الـ pickle ممكن ياخد ثواني.
  • Debugging: الـ stack traces في multiprocessing بتبقى أصعب في القراءة، والـ breakpoints مش بتشتغل بنفس البساطة.

الافتراض في الكلام ده إن شغلك CPU-bound حقيقي. لو 90% من وقت كودك في I/O، threading كافي تماماً ومفيش داعي تروح multiprocessing.

متى لا تستخدم threading أو multiprocessing

لو الـ workload بتاعك صغير (أقل من 50 مللي ثانية total)، التوازي مش هيفيدك. الـ overhead بتاع إنشاء الـ threads أو الـ processes هيبتلع أي مكسب. مثال: لو بتعالج 100 صف في DataFrame بحساب بسيط، اكتبها sequential وانسى الموضوع. التوازي يبقى منطقي لما الشغل الفردي يبقى ≥ 100ms ويبقى عندك على الأقل 4 وحدات شغل مستقلة.

الخطوة التالية

افتح أي script Python عندك بيستخدم threading.Thread لشغل CPU-bound. حدد مكان الحلقة الحاسبة، استبدلها بـ multiprocessing.Pool أو ProcessPoolExecutor. شغّل الـ benchmark قبل وبعد بـ time.perf_counter. لو الفرق أقل من 2x، يبقى الشغل I/O-bound من الأصل وعمرك ما كنت محتاج تكسر الـ GIL.

المصادر

  • Python Documentation - threading module: docs.python.org/3/library/threading.html
  • PEP 703 - Making the Global Interpreter Lock Optional in CPython: peps.python.org/pep-0703
  • Python 3.13 Release Notes (free-threaded build): docs.python.org/3.13/whatsnew/3.13.html
  • David Beazley - Understanding the Python GIL (PyCon 2010): dabeaz.com/python/UnderstandingGIL.pdf
  • CPython Source - ceval_gil.c: github.com/python/cpython

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة