0Pricing
Go Academy · درس

القنوات المخزَّنة

السعة وإغلاق القنوات والتكرار عبر القناة باستخدام range

القنوات المخزَّنة درس مجاني في Go Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Go Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Go Academy 4 دروس في المجموع.

ما هي القناة ذات المخزن المؤقت؟

تملك القناة ذات المخزن المؤقت سعةً، لذلك لا يُحظر الإرسال ما دام هناك مكان في المخزن. وتعمل القناة كأنها قائمة انتظار:

package main
import "fmt"

func main() {
    ch := make(chan int, 3)  // capacity 3

    ch <- 1  // does not block — buffer has room
    ch <- 2  // does not block
    ch <- 3  // does not block
    // ch <- 4  // would block — buffer full

    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
    fmt.Println(<-ch) // 3
}

القناة ذات المخزن المؤقت كقائمة انتظار

تطبّق القنوات ذات المخزن المؤقت قائمة انتظار FIFO بين الغوروتينات:

package main
import ("fmt"; "sync")

func producer(ch chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 1; i <= 5; i++ {
        ch <- i
        fmt.Printf("produced %d\n", i)
    }
}

func main() {
    ch := make(chan int, 2)  // buffer 2 — producer runs ahead
    var wg sync.WaitGroup
    wg.Add(1)
    go producer(ch, &wg)
    wg.Wait()
    close(ch)
    for v := range ch { fmt.Printf("consumed %d\n", v) }
}

len() و cap() على القنوات

افحصوا الحجم الحالي لقناة ذات مخزن مؤقت وسعتها:

package main
import "fmt"

func main() {
    ch := make(chan string, 5)
    ch <- "a"
    ch <- "b"
    ch <- "c"

    fmt.Println("cap:", cap(ch))  // 5
    fmt.Println("len:", len(ch))  // 3 — items currently buffered

    <-ch
    fmt.Println("len:", len(ch))  // 2
}

فصل المنتج عن المستهلك

يفصل المخزن المؤقت سرعة المنتج عن سرعة المستهلك. ويمكن للمنتج أن يتقدم بمقدار حجم المخزن قبل أن يُحظر:

package main
import ("fmt"; "sync"; "time")

func produce(ch chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for i := 0; i < 5; i++ {
        ch <- i
    }
}

func consume(ch <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for v := range ch {
        time.Sleep(10 * time.Millisecond)  // slower consumer
        fmt.Println("consumed:", v)
    }
}

func main() {
    ch := make(chan int, 3)
    var wg sync.WaitGroup
    wg.Add(2)
    go produce(ch, &wg)
    go consume(ch, &wg) // BUG: produce closes before consume sees all
    // In practice, coordinate close carefully
    fmt.Println("decoupled")
}

إشارة مرور باستخدام قناة ذات مخزن مؤقت

يمكن لقناة ذات مخزن مؤقت بسعة N أن تحدّ عدد الغوروتينات المتزامنة إلى N — وهي إشارة مرور بسيطة:

package main
import ("fmt"; "sync"; "time")

func main() {
    sem := make(chan struct{}, 3)  // max 3 concurrent
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        i := i
        go func() {
            defer wg.Done()
            sem <- struct{}{}    // acquire
            defer func() { <-sem }()  // release
            fmt.Printf("job %d running\n", i)
            time.Sleep(50 * time.Millisecond)
        }()
    }
    wg.Wait()
}

إغلاق قناة ذات مخزن مؤقت

يسمح إغلاق قناة ذات مخزن مؤقت للمستقبلين باستنزاف القيم المتبقية في المخزن قبل اكتشاف الإغلاق:

package main
import "fmt"

func main() {
    ch := make(chan int, 5)
    for i := 1; i <= 3; i++ { ch <- i }
    close(ch)

    // Drain all buffered values after close:
    for v := range ch {
        fmt.Println(v)  // 1, 2, 3
    }
    // range exits after channel closed and drained
}

المخزنة مؤقتًا مقابل غير المخزنة مؤقتًا: متى تختارون كل نوع

دليل الاختيار:

  • غير مخزنة مؤقتًا: تسليم متزامن مضمون، وترتيب صارم، ومزامنة عبر نقطة التقاء
  • ذات مخزن مؤقت: فصل سرعتي المنتج والمستهلك، وتقليل الحظر، وتطبيق إشارات المرور، وجمع N من النتائج
  • ابدؤوا افتراضيًا بقناة غير مخزنة مؤقتًا — وأضيفوا مخزنًا مؤقتًا فقط عندما يمكنكم تبرير حجمه
  • حجم المخزن الصغير جدًا ← يظل يسبب الحظر؛ والحجم الكبير جدًا ← يخفي الأخطاء

جمع النتائج باستخدام قناة ذات مخزن مؤقت

استخدموا قناة ذات مخزن مؤقت لجمع النتائج من الغوروتينات المتوازية من دون حظر:

package main
import ("fmt"; "sync")

func square(n int) int { return n * n }

func main() {
    nums := []int{1, 2, 3, 4, 5}
    results := make(chan int, len(nums))  // buffer = goroutine count
    var wg sync.WaitGroup

    for _, n := range nums {
        wg.Add(1)
        n := n
        go func() {
            defer wg.Done()
            results <- square(n)
        }()
    }
    wg.Wait()
    close(results)

    for r := range results { fmt.Println(r) }
}

تسرّب الغوروتين مع امتلاء المخزن المؤقت

إذا كان المخزن المؤقت ممتلئًا ولم يوجد قارئ، فسيُحظر المرسل إلى الأبد — مما يؤدي إلى تسرّب غوروتين:

package main
import ("fmt"; "time"; "runtime")

func main() {
    ch := make(chan int, 2)
    // Start goroutine that fills buffer and then blocks
    go func() {
        ch <- 1
        ch <- 2
        ch <- 3  // blocks: buffer full, no receiver
        fmt.Println("never reached")
    }()
    time.Sleep(50 * time.Millisecond)
    fmt.Println("goroutines:", runtime.NumGoroutine()) // 2 — leaked
}

time.After كقناة ذات مخزن مؤقت

تعيد time.After قناة من النوع <-chan time.Time ذات مخزن مؤقت بسعة 1. وترسل بيئة التشغيل إليها بعد انقضاء المدة، ثم تُجمع القناة بواسطة جامع البيانات المهملة:

package main
import ("fmt"; "time")

func doWithTimeout(work func() string) (string, bool) {
    result := make(chan string, 1)
    go func() { result <- work() }()
    select {
    case r := <-result:
        return r, true
    case <-time.After(100 * time.Millisecond):
        return "", false
    }
}

func main() {
    r, ok := doWithTimeout(func() string {
        time.Sleep(50 * time.Millisecond)
        return "done"
    })
    fmt.Println(r, ok)
}

تحقق سريع

ماذا يحدث عند الإرسال إلى قناة ذات مخزن مؤقت بلغت سعتها القصوى؟

مراجعة: القنوات ذات المخزن المؤقت

الملخص:

  • تنشئ make(chan T, N) قناة ذات مخزن مؤقت بسعة N
  • يُحظر الإرسال فقط عندما يمتلئ المخزن؛ ويُحظر الاستقبال فقط عندما يكون فارغًا
  • len(ch) — عدد العناصر الحالية؛ وcap(ch) — السعة
  • استخدموها لفصل المنتجين عن المستهلكين، وجمع النتائج المتوازية، وتطبيق إشارات المرور
  • يسمح الإغلاق باستنزاف القيم قبل EOF؛ ويتولى range ch ذلك تلقائيًا

الأسئلة الشائعة

هل درس «القنوات المخزَّنة» مجاني؟

نعم — نص درس «القنوات المخزَّنة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Go Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Go Academy 4 دروس في المجموع.

ماذا ستتعلم في «القنوات المخزَّنة»؟

السعة وإغلاق القنوات والتكرار عبر القناة باستخدام range تتمرن على Go Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Go Academy؟

لا تُشترط خبرة سابقة. Go Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «القنوات المخزَّنة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Go Academy هذا؟

نعم. كل درس في Go Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تشغيل Goroutines
  2. القنوات غير المخزَّنة
  3. القنوات المخزَّنة
  4. اتجاه القنوات وأنماط خطوط الأنابيب
← العودة إلى Go Academy