แชนเนลแบบมีบัฟเฟอร์
ความจุ การปิดแชนเนล และการวนซ้ำแชนเนลด้วย 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การเริ่มใช้กอรูทีน
- แชนเนลแบบไม่มีบัฟเฟอร์
- แชนเนลแบบมีบัฟเฟอร์
- ทิศทางแชนเนลและรูปแบบไปป์ไลน์