Go / دليل حلول تحريري

حل تسرب الروتينات والجمود وفجوات الإلغاء

اعثر على العملية الحاجبة التي لا تملك اكتمالا قابلا للبلوغ وأصلح الإلغاء والانتظار معا.

وقت القراءة التقريبي: 3 دقيقةتاريخ مراجعة المحتوى

قاعدة معرفة للقراءة فقط · بلا تعليقات عامة

تعرّف على الأعراض

يعود طلب ويبقى عاملون مملوكون محجوبين، أو لا يكتمل الإنهاء، أو يبلغ برنامج محلي "fatal error: all goroutines are asleep - deadlock!". لا تضمن كل حالة جمود هذه الرسالة؛ فقد تبقي المؤقتات أو الشبكة أو روتينات أخرى العملية حية والعمل معلقا.

افهم الأسباب المحتملة

  • قد يفتقد الإرسال مستقبلا أو ينتظر الاستقبال قناة لا تغلق أو تمنع nil التقدم. قد يهمل الحجب الإلغاء ولا ينضم cancel للروتينات. تؤخر المخازن غياب المستهلك دون تعريف الإنهاء، وقد يسبب إغلاق مستقبل عشوائي إرسالا لمغلقة.

1. حصر الروتينات المملوكة ونقاط الحجب

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

2. تصنيف حالة القناة وملكية الإغلاق

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

3. إضافة الإلغاء والاكتمال الصريح

ينتظر المثال الإلغاء بجانب استقبال المدخل. المدخل nil عمدا فلا تتقدم حالته؛ ويوفر الإلغاء الخروج. تلغي main ثم تنتظر done قبل التقرير. يعرض ذلك غياب المدخل بأمان دون تنفيذ جمود متعمد. يملك العامل إغلاق done ولا يغلق المدخل.

go عامل مدخله nil قابل للإلغاء والانتظار
package main

import (
	"context"
	"fmt"
)

func start(ctx context.Context, input <-chan int) <-chan struct{} {
	done := make(chan struct{})
	go func() {
		defer close(done)
		for {
			select {
			case <-ctx.Done():
				return
			case _, ok := <-input:
				if !ok {
					return
				}
			}
		}
	}()
	return done
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	var input <-chan int
	done := start(ctx, input)
	cancel()
	<-done
	fmt.Println("worker joined")
}
الناتج المتوقع
worker joined

تُعطل حالة nil وينهي الإلغاء العامل وتقر done عودته.

4. تغطية كل مرحلة حاجبة

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

5. تحديد العودة المبكرة والنتائج الجزئية

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

تحقق من نجاح الإصلاح

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

امنع تكرار المشكلة

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

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