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

Go / المتوسط: الحزم والعمل المتزامن / الدرس 12

الروتينات والقنوات والاكتمال

نسق العمل المتزامن بالاتصال وملكية القنوات والاكتمال الملحوظ بدلا من النوم.

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

أهداف الدرس

  • شرح إطلاق الروتين وقيم الوسائط المشتركة.
  • تمييز القنوات المخزنة وغير المخزنة وnil والمغلقة.
  • تعيين مسؤول إغلاق واحد وانتظار العاملين.

الإطلاق لا ينتظر

تبدأ go استدعاء دالة في روتين جديد. تُقيّم الوسائط في الروتين المطلق وتمرر قيمها، وقد يظل التخزين المشار إليه مشتركا. يتابع المستدعي دون انتظار. الروتين أخف من خيط نظام في تنفيذات شائعة لكنه ليس مجانا ولا محدودا تلقائيا.

تنهي عودة main البرنامج ولو بقيت روتينات. حدد من يبدأ كل روتين وما ينهيه ومن ينتظره. النوم لمدة مخمنة ليس بروتوكول اكتمال ولا يثبت سلامة الحالة المشتركة.

القناة تحمل قيما وتنسيقا

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

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

go منتج بمالك إغلاق واحد
package main

import "fmt"

func main() {
	values := make(chan int)
	go func() {
		defer close(values)
		values <- 2
		values <- 4
	}()
	total := 0
	for value := range values {
		total += value
	}
	fmt.Println("sum", total)
}
الناتج المتوقع
sum 6

يُستقبل الإرسالان قبل ملاحظة الإغلاق فينتج مجموع ستة.

الإغلاق لا يتخلص من البيانات المخزنة

يعلن الإغلاق عدم إرسال مزيد ولا يحذف المخزن السابق. بعد استنزاف القيم تعيد القراءة الصفر فورا مع ok=false. ينتهي range بعد استهلاك المخزن. يمكن اجتياز قناة قراءة فقط ولا يمكن إغلاقها عبر قيمتها المقيدة.

يظهر المثال الثاني حالة الاستنزاف. يسبب الإرسال لمغلقة هلعا وكذلك إغلاق مغلقة سابقا أو nil. يحجب استقبال nil للأبد وتعطل حالتها في select. الحالات مختلفة وليست nil تهجئة أخرى للمغلقة.

go ملاحظة مخزن مغلق ومستنزف
package main

import "fmt"

func main() {
	queue := make(chan int, 2)
	queue <- 1
	queue <- 2
	close(queue)
	total := 0
	for value := range queue {
		total += value
	}
	value, ok := <-queue
	fmt.Println("sum", total, "drained", value, ok)
}
الناتج المتوقع
sum 3 drained 0 false

تبقى الواحد والاثنان بعد الإغلاق؛ ويعيد الاستقبال الأخير صفرا وfalse.

القنوات تعبر عن سياسة سعة أيضا

تقيد المعاملات الاتجاهية عمليات الدالة: chan<- T للإرسال و<-chan T للاستقبال. لا يحدد الاتجاه وحده مالك الإغلاق فوثقه. مع مرسلين متعددين يجب أن يعرف منسق انتهاء الجميع قبل إغلاق الخرج.

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

فصل تدفق القيم من اكتمال العامل

يدل إغلاق الخرج على اكتمال المنتج فقط إذا نص البروتوكول أنه بعد كل العمل والتنظيف. قد تكون done أو WaitGroup منفصلة أوضح. تختار select بين الاتصالات الجاهزة وقد تشمل الإلغاء، ولا يعطي ترتيب المصدر أولوية.

اختبر مستهلكا غائبا ومنتجا فارغا وإلغاء مبكرا وانتهاء الجميع. اشترط لكل روتين مسار خروج محدودا. لا تجعل استرداد هلع الإرسال لمغلقة بروتوكول الإنهاء المعتاد؛ فهو يخفي خطأ ملكية وقد يضيع قيما بلا سياسة.

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

يخرج البرنامج أحيانا قبل نتيجة العامل.

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

يغلق مستقبل والمرسل نشط.

لماذا يحدث؟
لم تُحدد ملكية الإغلاق والاكتمال.
كيف تصلحه؟
ليغلق المنتج أو المنسق بعد الجميع.
كيف تتجنب تكراره؟
وثق مسؤول إغلاق واحدا.

تؤخر إضافة مخزن التعليق فقط.

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

اختبر فهمك

1. يرسل ثلاثة عاملين لخرج واحد. صف إغلاقا آمنا.

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

يتتبع منسق الجميع بـWaitGroup مع الإضافات قبل الإطلاق. يقر كل عامل الاكتمال بعد آخر إرسال محتمل. ينتظر المنسق ثم يغلق مرة واحدة ويستهلك المستقبلون فقط. وإذا توقف الاستقبال مبكرا فيجب أن تلاحظ الإرسالات الإلغاء لينتهي المنسق.

2. لماذا يعلق range على قناة لم تغلق بعد قراءة مخزونها؟

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

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

أهم ما تعلمته

  • يحتاج كل روتين حدث توقف ومالك اكتمال.
  • تختلف القنوات nil والمفتوحة والمغلقة والمستنزفة.
  • تحد المخازن القيم المنتظرة ولا تصلح غياب بروتوكول العمر.

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