Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

sync.WaitGroup

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 64 من 107.

في درس أساسيات Goroutines، استخدمنا time.Sleep للانتظار حتى تنتهي goroutines: وهو أسلوب هش. توفّر sync.WaitGroup طريقة مناسبة للانتظار حتى complete مجموعة من goroutines عملها.

يعمل WaitGroup مثل counter. تقوم بعملية increment له before بدء goroutine، وتقوم بعملية decrement له عند انتهاء goroutine، وتظل في حالة block حتى يصل counter إلى الصفر:

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1)  // زيادة العداد
        go func(id int) {
            defer wg.Done()  // إنقاص عند الانتهاء
            fmt.Printf("Worker %d finished\n", id)
        }(i)
    }
    
    wg.Wait()  // حظر حتى يصبح العداد 0
    fmt.Println("All workers complete")
}

الطرق الثلاثة الأساسية هي Add(n) لزيادة counter بمقدار n، وDone() لتقليله بمقدار واحد (وهو ما يعادل Add(-1))، وWait() لتنفيذ block حتى يصل counter إلى الصفر. يضمن استخدام defer wg.Done() تناقص counter حتى إذا حدث panic في goroutine.

من الأخطاء الشائعة استدعاء Add داخل goroutine بدلًا من استدعائه قبلها. يؤدي ذلك إلى إنشاء حالة تسابق قد تُرجع فيها Wait النتيجة قبل تسجيل جميع goroutines:

// خطأ - حالة سباق
go func() {
    wg.Add(1)  // قد يعمل بعد Wait()
    defer wg.Done()
}()

// صحيح - Add قبل الإطلاق
wg.Add(1)
go func() {
    defer wg.Done()
}()

غالبًا ما يتم تضمين WaitGroups في البُنى لتنسيق العمليات concurrent، مما يجعلها أساسية لبناء أنواع آمنة للخيوط في Go.

challenge icon

التحدي

سهل

لنبنِ مدير تنزيلات ينسّق عمليات تنزيل file متعددة ومتزامنة باستخدام sync.WaitGroup. سيتتبّع مديرك متى تكتمل جميع عمليات التنزيل دون الاعتماد على مؤقتات نوم عشوائية.

ستنظّم شفرتك عبر ملفين:

  • downloader.go: عرّف منطق تنسيق التنزيلات لديك.

    أنشئ بنية Download مع حقول ID (int) وFilename (string) وSize (int، تمثّل KB).

    Implement أسلوبًا Process(wg *sync.WaitGroup) على Download يحاكي تنزيل file من خلال النوم لمدة تعتمد على حجم file (استخدم Size * 10 ميلي ثانية). عند اكتمال التنزيل، اطبع: Downloaded: [Filename] ([Size]KB). استخدم defer wg.Done() لضمان إنقاص عدّاد WaitGroup بشكل صحيح.

    أنشئ function StartDownloads(downloads []Download) int تطلق جميع التنزيلات concurrently باستخدام goroutines وتنتظر حتى تكتمل كلها. تذكّر استدعاء wg.Add(1) قبل إطلاق كل goroutine، وليس بداخلها. أعد الحجم الإجمالي لجميع التنزيلات مجتمعة.

  • main.go: اقرأ information التنزيلات ونظّم التنزيلات concurrent.

    اقرأ عدد التنزيلات، ثم اقرأ لكل تنزيل معرّفه واسم file وحجمه. أنشئ التنزيلات ومرّرها إلى StartDownloads. بعد اكتمال جميع التنزيلات، اطبع: All downloads complete: [total]KB total

سيتم توفير المدخلات التالية:

  • السطر 1: عدد التنزيلات (عدد صحيح)
  • الأسطر التالية: لكل تنزيل، ثلاثة أسطر - المعرّف (عدد صحيح)، واسم file (سلسلة نصية)، والحجم بوحدة KB (عدد صحيح)

على سبيل المثال، إذا أُعطيت:

3
1
report.pdf
20
2
image.png
5
3
data.csv
10

يجب أن يُظهر الناتج اكتمال التنزيلات (تنتهي الملفات الأصغر أولًا بسبب أزمنة النوم الأقصر)، متبوعًا بالملخص:

Downloaded: image.png (5KB)
Downloaded: data.csv (10KB)
Downloaded: report.pdf (20KB)
All downloads complete: 35KB total

الفرق الأساسي عن استخدام time.Sleep هو أن wg.Wait() يحجب التنفيذ حتى تشير جميع goroutines إلى اكتمالها باستخدام Done()، مما يمنحك تزامنًا دقيقًا بغضّ النظر عن المدة التي يستغرقها كل تنزيل.

جرّب بنفسك

package main

import (
	"fmt"
)

func main() {
	// اقرأ عدد التنزيلات
	var n int
	fmt.Scanln(&n)

	// اقرأ معلومات التنزيل
	downloads := make([]Download, n)
	for i := 0; i < n; i++ {
		var id int
		var filename string
		var size int
		fmt.Scanln(&id)
		fmt.Scanln(&filename)
		fmt.Scanln(&size)
		
		downloads[i] = Download{
			ID:       id,
			Filename: filename,
			Size:     size,
		}
	}

	// TODO: استدعِ StartDownloads مع شريحة downloads
	// TODO: Print the summary: "All downloads complete: [total]KB total"
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت