0Pricing
Go Academy · บทเรียน

แชนเนลแบบมีบัฟเฟอร์

ความจุ การปิดแชนเนล และการวนซ้ำแชนเนลด้วย range

แชนเนลแบบมีบัฟเฟอร์ เป็นบทเรียน Go Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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 ระหว่าง goroutine:

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
}

การแยกความเร็วของ producer และ consumer

บัฟเฟอร์แยกความเร็วของ producer ออกจาก consumer ได้ producer สามารถทำงานนำหน้าได้ตามขนาดบัฟเฟอร์ก่อนจะถูกบล็อก:

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 สามารถจำกัดจำนวน goroutine ที่ทำงานพร้อมกันไว้ที่ 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()
}

การปิดช่องสัญญาณมีบัฟเฟอร์

การปิดช่องสัญญาณมีบัฟเฟอร์ทำให้ receiver รับค่าที่เหลืออยู่ในบัฟเฟอร์จนครบได้ ก่อนตรวจพบการปิด:

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
}

มีบัฟเฟอร์เทียบกับไม่มีบัฟเฟอร์: ควรเลือกใช้เมื่อใด

แนวทางการตัดสินใจ:

  • ไม่มีบัฟเฟอร์: การส่งต่อแบบซิงโครนัสที่รับประกันได้ การเรียงลำดับที่เคร่งครัด และการซิงโครไนซ์แบบนัดพบ
  • มีบัฟเฟอร์: แยกความเร็วของ producer และ consumer ลดการบล็อก ใช้สร้างเซมาฟอร์ และรวบรวมผลลัพธ์ N รายการ
  • ให้ใช้แบบไม่มีบัฟเฟอร์เป็นค่าเริ่มต้น และเพิ่มบัฟเฟอร์เมื่ออธิบายเหตุผลของขนาดที่เลือกได้เท่านั้น
  • บัฟเฟอร์ที่เล็กเกินไป → ยังคงเกิดการบล็อก ส่วนบัฟเฟอร์ที่ใหญ่เกินไป → ซ่อนข้อผิดพลาด

การรวบรวมผลลัพธ์ด้วยช่องสัญญาณมีบัฟเฟอร์

ใช้ช่องสัญญาณมีบัฟเฟอร์เพื่อรวบรวมผลลัพธ์จาก goroutine ที่ทำงานพร้อมกันโดยไม่ทำให้เกิดการบล็อก:

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) }
}

การรั่วไหลของ goroutine เมื่อบัฟเฟอร์เต็ม

หากบัฟเฟอร์เต็มและไม่มี reader อยู่ sender จะถูกบล็อกตลอดไป ซึ่งทำให้เกิดการรั่วไหลของ goroutine:

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) — ความจุ
  • ใช้เพื่อแยก producer และ consumer ออกจากกัน รวบรวมผลลัพธ์จากการทำงานพร้อมกัน และสร้างเซมาฟอร์
  • การปิดช่องสัญญาณทำให้รับค่าที่เหลือได้จนถึง EOF ส่วน range ch จะจัดการเรื่องนี้โดยอัตโนมัติ

คำถามที่พบบ่อย

บทเรียน “แชนเนลแบบมีบัฟเฟอร์” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “แชนเนลแบบมีบัฟเฟอร์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Go Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Go Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แชนเนลแบบมีบัฟเฟอร์”

ความจุ การปิดแชนเนล และการวนซ้ำแชนเนลด้วย range คุณปฏิบัติ Go Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Go Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Go Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “แชนเนลแบบมีบัฟเฟอร์” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Go Academy นี้ได้ไหม

ได้ บทเรียน Go Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การเริ่มใช้กอรูทีน
  2. แชนเนลแบบไม่มีบัฟเฟอร์
  3. แชนเนลแบบมีบัฟเฟอร์
  4. ทิศทางแชนเนลและรูปแบบไปป์ไลน์
← กลับไปที่ Go Academy