محتويات الدورة

بايثون / أنظمة قابلة للصيانة / الدرس 16

اللاتزامن والخيوط والعمليات وقفل GIL

اختر التزامن بحسب الحمل، وأدر أعمار المهام، وميز سلوك بناء CPython عن ضمانات اللغة العامة.

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • التزامن اختيار جدولة
  • الحالة المشتركة تحتاج إلى تصميم

التزامن اختيار جدولة

يسمح التزامن بتقدم أنشطة متداخل، بينما ينفذ التوازي العمل آنيا على موارد متعددة. تجدول asyncio عادة مهام تعاونية في خيط حلقة واحد. قد تعلق await المهمة ليتقدم غيرها، لكن الحساب المكثف دون تعليق يحجب الحلقة. ينشئ استدعاء دالة async كائن coroutine ولا ينجز جسمها فورا. تملك asyncio.run الحلقة العليا في برنامج عادي؛ أما داخل حلقة جارية فاستخدم await بدلا من تعشيش run. اختر async للعمل القابل لانتظار عمليات غير حاجبة، لا لمجرد بطئه.

تحديد عمر المهام والإلغاء

قدمت 3.11 مجموعة TaskGroup لتحديد نطاق مهام مترابطة وانتظارها عند الخروج. قد يلغي فشل مهمة المهام الشقيقة، وتمثل الأخطاء المجمعة وفق قواعد مجموعات الاستثناء. استخدم finally للتنظيف، وأعد عادة رفع CancelledError بعده حتى يعمل التزامن المنظم. يعبر الإلغاء والمهلة عن طلب توقف تعاوني، لا عن إلغاء آثار تمت. حدد التزامن بإشارة أو طابور عمال؛ فقد يستنفد إنشاء مهمة لكل مدخل غير محدود الذاكرة حتى لو أمضت معظم الوقت منتظرة.

مثال لاتزامني محدد

ينشئ المثال مهمتين مترابطتين داخل TaskGroup ولا يطبع النتيجة حتى الخروج. توفر sleep(0) تعليقا تعاونيا دون شبكة أو ادعاء زمني. يتبع التقرير قائمة المهام الصريحة، لا ترتيب طباعة داخلي يتغير بالجدولة. يثبت المثال عمر المهام ولا يدعي تسريع تربيع عددين باللاتزامن. في العمل الفعلي حدد المورد المنتظر وتوافق عميله مع async، وقس الإنتاجية والكمون بتزامن محدود بدلا من استنتاج الأداء من الكلمات المفتاحية.

python مثال لاتزامني محدد — مثال محلول
import asyncio

async def square(value):
    await asyncio.sleep(0)
    return value * value

async def main():
    async with asyncio.TaskGroup() as group:
        tasks = [group.create_task(square(value)) for value in [2, 3]]
    print([task.result() for task in tasks])

asyncio.run(main())
الناتج المتوقع
[4, 9]

تنتظر المجموعة المهمتين. تمنع قراءة النتائج بترتيب المدخل الاعتماد على المهمة التي استؤنفت أولا.

الخيوط والعمليات وحقائق GIL الخاصة بالبناء

في بناء CPython التقليدي ذي GIL، يحد القفل تنفيذ بايتكود بايثون المتزامن بين الخيوط داخل مفسر واحد، وتحرره عمليات إدخال وإخراج وإضافات أصلية عديدة. تفيد الخيوط العمل المنتظر، وتناسب العمليات المنفصلة غالبا حساب بايثون المكثف بعد حساب كلفة البدء ونقل البيانات. ليست القاعدة أن خيوط بايثون لا تتوازى حسابيا أبدا. قدمت CPython 3.13 بناء اختياريا دون GIL وتدعمه 3.14 أيضا، وقد تعيد إضافات تفعيله. افحص البناء وتوافق الإضافات، لا رقم الإصدار وحده.

الحالة المشتركة تحتاج إلى تصميم

لا يعوض GIL مزامنة التطبيق؛ فقد تتكون القراءة والتعديل والكتابة من عمليات عدة تنتهك الثوابت. تزيد الخيوط الحرة أهمية المزامنة الصريحة؛ فلا تعامل أقفال الحاويات الداخلية كمعاملة تطبيق. فضل المدخلات المستقلة والنتائج المعادة، واستخدم أقفالا أو طوابير عند المشاركة. يوضح المثال مجمع خيوط صغيرا بنتائج map مرتبة. تضيف مجمعات العمليات شروط الاستيراد ونقل الكائنات، وتهم حماية نقطة الدخول على منصات spawn. قس كلفة التنسيق قبل زيادة العمال، واسترجع أخطاء future دائما.

python الحالة المشتركة تحتاج إلى تصميم — مثال محلول
from concurrent.futures import ThreadPoolExecutor

def square(value):
    return value * value

with ThreadPoolExecutor(max_workers=2) as executor:
    results = list(executor.map(square, [2, 3, 4]))
print(results)
الناتج المتوقع
[4, 9, 16]

لا تحتاج المهام المستقلة مجمّعا مشتركا. تعطي map النتائج بترتيب المدخل وينتظر سياق المنفذ العمل المقدم. لا يدعي المثال تحسنا في السرعة.

الأخطاء الشائعة وإصلاحها

توقف المهام الأخرى أثناء عملية واحدة

لماذا يحدث؟
يشغل عمل متزامن حاجب أو حسابي خيط الحلقة.
كيف تصلحه؟
استخدم عملية متوافقة أو انقل العمل المناسب إلى منفذ محدود.
كيف تتجنب تكراره؟
حدد حدود الحجب قبل اعتماد asyncio.

رسالة coroutine was never awaited

لماذا يحدث؟
أنشأ الاستدعاء كائنا دون انتظاره أو جدولته.
كيف تصلحه؟
انتظره أو أنشئه داخل نطاق مهام مملوك.
كيف تتجنب تكراره؟
وضح مالك كل مهمة ومسار اكتمالها.

فقدان تحديثات مشتركة رغم GIL

لماذا يحدث؟
افترض التطبيق ذرية ثابت متعدد الخطوات.
كيف تصلحه؟
استخدم مزامنة صريحة أو اجمع نتائج مستقلة.
كيف تتجنب تكراره؟
اختبر التصميم دون الاعتماد على جدولة عرضية.

اختبر فهمك

1. اختر نموذجا لعمليات حاجبة مستقلة ولحسابات بايثون المكثفة.

اعرض الحل المشروح

قد يداخل مجمع خيوط محدود العمليات الحاجبة. للحساب المكثف على CPython ذي GIL، ادرس العمليات بعد قياس النقل والبدء. وقد تتوازى الخيوط حسابيا في بناء حر تدعمه الاعتماديات. قس الحمل والمفسر الفعليين.

2. لماذا تعيد معالجات الإلغاء عادة رفع CancelledError بعد التنظيف؟

اعرض الحل المشروح

يعتمد التزامن المنظم والمهل على انتشار الإلغاء لتتبع الاكتمال. قد يبقي ابتلاعه العمل حيا أو يربك عمر النطاق. ضع التحرير في finally ولا تخف الإلغاء إلا بسياسة متقدمة مقصودة موثقة.

أهم ما تعلمته

  • طابق التزامن مع الانتظار والحساب والكلفة.
  • ملكية المهام والإلغاء جزء من الصحة.
  • يعتمد GIL على بناء CPython والإضافات.

المراجع الرسمية