1. Tai sao lai co hai che do: khong buffer va co buffer
Neu nhin channel bang mat cua nguoi moi bat dau, ban se muon chung hoat dong nhu mot “ong dan”: nem mot gia tri vao — va no bay di dau do, khong can can tro gi ca. Nhung Go duoc thiet ke mot cach thuc te: doi khi ban can dong bo chat che “chung ta da gap nhau va da truyen gia tri”, va doi khi can queue de lam mem su chenh lech toc do giua nguoi gui va nguoi nhan. Vi ly do do, Go co unbuffered va buffered channels — cung la mot cong cu, nhung co hanh vi khac nhau.
Cau truc chi khac nhau boi mot con so:
- make(chan int) — channel unbuffered (suc chua 0)
- make(chan int, 3) — channel buffered (suc chua 3)
Va “mot con so” nay thay doi viec goroutine se phai doi nhau khi nao.
Unbuffered channel: “truyen nguoc chieu”
Unbuffered channel (make(chan T)) co the hinh dung nhu truyen mot vat tu tay nay sang tay kia. Neu ban dua mot cai USB cho nguoi khac ma khong co ai o do — ban se dung voi tay dua ra. Neu nguoi kia san sang nhan USB ma ban chua dua ra — ho se dung voi tay dua ra. Ca hai ben deu phai “gap nhau” trong thoi gian.
Trong ngon ngu Go, dieu do co nghia la: send vao unbuffered channel bi block cho den khi co ai do bat dau receive, va nguoc lai, receive bi block cho den khi co ai do send.
Hay nhin no nhu mot mini-dialog (sequence diagram):
sequenceDiagram
participant S as Sender goroutine
participant C as Channel (unbuffered)
participant R as Receiver goroutine
S->>C: ch <- 42
Note over S,C: block, while no receive
R->>C: v := <-ch
Note over C,R: value transferred
Note over S: sender unblocked
Vi du nho: “bat tay” trong code
package main
import (
"fmt"
)
func main() {
ch := make(chan int) // unbuffered
go func() {
ch <- 42 // will wait until main reads
}()
v := <-ch
fmt.Println(v) // 42
}
O day main nhan gia tri va chi bang cach do “giai phong” nguoi gui. Cam giac quan trong: unbuffered channel khong phai la queue, ma la dong bo.
Viec nay co ich nhu the nao, khong phai “bat tien”?
Vi doi khi ban that su can: “dung tiep tuc lam viec cho den khi goroutine kia xac nhan rang gia tri da duoc nhan”. Vi du, ban muon dam bao thu tu cac buoc: dau tien chuan bi du lieu, sau do truyen, sau do tiep tuc. Unbuffered channel la mot “barrier” tich hop nhu vay, khong can Mutex va khong can bo nho chung.
2. Buffered channel: queue va dich chuyen diem block
Buffered channel (make(chan T, n)) co the hinh dung nhu mot hop thu voi n thu. Ban co the bo mot buc thu vao do va di ngay, ke ca khi nguoi nhan van dang ngu. Nhung neu hop da day kin, ban se khong the nhan them buc thu nua: phai doi ai do don don thu.
Day la ban chat:
- send (ch <- v) bi block khi buffer day
- receive (<-ch) bi block khi buffer rong (va khong co ai dang send ngay luc do)
Tuc la buffer khong xoa bo block, no chi doi diem xay ra.
Vi du nho: “queue 2 phan tu”
package main
import "fmt"
func main() {
ch := make(chan string, 2) // buffered, cap=2
ch <- "A" // does not block
ch <- "B" // does not block
fmt.Println(<-ch) // A
fmt.Println(<-ch) // B
}
O day cac lan send khong phai doi nguoi nhan, vi buffer van chua day.
Vi du nho: cach thay block khi buffer day
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 1 // buffer is full
go func() {
fmt.Println(<-ch) // 1
}()
ch <- 2 // wait until "1" is read
fmt.Println(<-ch) // 2
}
Ngay khi gia tri 1 duoc doc, cho trong buffer duoc giai phong, va lan send 2 co the hoan tat.
Chi tiet thuc te quan trong: “buffer 1” nhu mot cach khong giu goroutine lam tin do
Doi khi mot goroutine can gui mot signal va ket thuc, ke ca neu ben kia chua san sang nhan signal o cung mili giay do. Khi do nguoi ta tao channel voi buffer 1: sender bo gia tri vao, thoat ra, va khong bi treo trong luc cho nguoi nhan.
O day chung ta chua di sau vao cac mo hinh cho doi nhieu su kien; chi can hieu nguyen ly: buffer nho doi khi khong phai “de tang toc do”, ma de cho goroutine co the ket thuc ma khong bi treo.
4. Cac nuance thuc te: block, len/cap va lua chon
Khi nao thi ai bi block
Neu co gang ghi nho bang cam giac, ban co the nham lan nguoi nao dang doi nguoi nao. Vi vay, mot bang nho rat huu ich. O day khong co ma thuat: chi co hai cau hoi — “co cho de bo vao khong?” va “co gi de lay ra khong?”.
So sanh:
| Loai channel | cap(ch) | Khi nao ch <- v bi block | Khi nao <-ch bi block |
|---|---|---|---|
| Unbuffered | |
Luc nao cung vay, cho den khi co nguoi nhan | Luc nao cung vay, cho den khi co nguoi gui |
| Buffered (n) | |
Khi buffer day (len == cap) | Khi buffer rong (len == 0) va khong co ai dang send |
Va day la y chinh cua bai giang: unbuffered channel la dong bo “da gap nhau”, con buffered channel la queue “tich tru va phan phoi”.
So do nho de nho
flowchart TD
A["send: ch <- v"] --> B{"cap(ch) == 0?"}
B -- yes --> C["unbuffered: wait for receive"]
B -- no --> D{"len(ch) < cap(ch)?"}
D -- yes --> E["put into buffer, continue"]
D -- no --> F["buffer full: wait for receive"]
G["receive: <-ch"] --> H{"len(ch) > 0?"}
H -- yes --> I["take from buffer/transfer"]
H -- no --> J{"cap(ch) == 0?"}
J -- yes --> K["unbuffered: wait for send"]
J -- no --> L["buffered: wait for send (buffer empty)"]
len(ch) va cap(ch): chan do, nhung khong phai “bang dieu khien”
Khi ban lan dau nhin len(ch) va cap(ch), se co cam giac: “A! Vay la co the viet if len(ch) > 0 { ... } va lam doc khong block”. Day la mot y tuong rat nguoi — va rat nguy hiem, vi trong moi truong dong thoi, “giua if va hanh dong” co the du cho the gioi thay doi.
Hay bat dau bang hieu dung:
- cap(ch) — suc chua buffer cua channel (0 doi voi unbuffered)
- len(ch) — co bao nhieu gia tri dang nam trong buffer ngay luc nay
Vi du nho: chan do buffer
package main
import "fmt"
func main() {
ch := make(chan int, 3)
fmt.Println(len(ch), cap(ch)) // 0 3
ch <- 10
ch <- 20
fmt.Println(len(ch), cap(ch)) // 2 3
}
Dieu nay huu ich cho viec debug va giai thich dieu dang dien ra. Nhung no khong phai la “dong bo dang tin cay”.
Tai sao? Vi dong thoi la khi nhieu goroutine co the thay doi trang thai gan nhu cung luc. Neu ban kiem tra len(ch), roi sau do quyet dinh lam gi do, mot goroutine khac co the da doc/ghi gia tri, va ket luan cua ban da loi thoi.
Trong Go code thuc te, len(ch) thuong dong vai tro cua mot “nhiet ke”: co the do nhiet do, nhung khong the dieu khien thoi tiet.
Cach chon: unbuffered hay buffered
Luc moi bat dau, rat muon co mot quy tac don gian: “Buffered channel — nhanh hon, vay ta se dung buffered”. Nhung buffer khong phai la nang suat mien phi. Buffer la thay doi giao thuc.
Neu ban dat buffer lon “de chac chan khong bi block”, ban dang cho phep he thong tich luy cong viec. Va viec tich luy co the vo han… cho den khi het bo nho hoac cho den khi chuong trinh bien thanh kho luu tru cac tac vu chua lam xong.
Cach nghi tot ve viec lua chon la nhu sau:
Unbuffered channel huu ich khi ban muon dong bo truc tiep va co “backpressure” tu nhien: sender khong the chay qua nhanh trong khi receiver chua kip xu ly. Dieu nay thuong lam hanh vi he thong du doan hon.
Buffered channel huu ich khi ban co ly do ro rang de “tach ro ve thoi gian” sender va receiver. Vi du, sender lam viec theo tung dot (burst), con receiver xu ly deu deu, va mot buffer nho lam mem cac dinh. Hoac ban muon mot goroutine song ngan co the gui ket qua va ket thuc, khong phu thuoc vao timing chinh xac cua receiver (buffer mau 1 nhu “hop thu mot buc thu”).
Quan trong la cau hoi “kich thuoc buffer bao nhieu” da la mot phan cua thiet ke: chung ta san sang giu bao nhieu phan tu trong hang doi cho? Doi khi cau tra loi la 1, doi khi la 10, doi khi la 0. Nhung gan nhu chua bao gio cau tra loi la “mot trieu, de khoi can suy nghi”.
5. Vi du: tong hop ket qua xu ly tac vu
De khong dung lai o muc tru tuong “hop thu”, hay dua channel vao mot phan nho cua ung dung hoc tap cua chung ta (tam coi nhu task manager). Gia su chung ta co mot danh sach tac vu, va muon tinh song song “trong luong” cho moi tac vu (vi du, do dai tieu de), sau do tong hop ket qua ve main.
Tam thoi chung ta chua dung dong kenh va for range ch — day la mot chu de khac. O day, chung ta se tong hop ket qua bang so lan doc co dinh: dung bang so luong tac vu.
Buoc 1: mo hinh du lieu va ket qua
package main
type Task struct {
ID int
Title string
}
type TaskStat struct {
ID int
TitleSize int
}
Buoc 2: xu ly mot tac vu
package main
func calcStat(t Task) TaskStat {
return TaskStat{
ID: t.ID,
TitleSize: len(t.Title),
}
}
Buoc 3: unbuffered channel — dong bo chat che
package main
import "fmt"
func main() {
tasks := []Task{{1, "Buy milk"}, {2, "Write Go"}, {3, "Sleep"}}
ch := make(chan TaskStat) // unbuffered
for _, t := range tasks {
go func(task Task) {
ch <- calcStat(task) // wait until main reads
}(t)
}
for i := 0; i < len(tasks); i++ {
stat := <-ch
fmt.Println(stat.ID, stat.TitleSize)
}
}
Dieu can cam nhan: ke ca neu mot goroutine nao do tinh ra ket qua rat nhanh, no khong the “do” ket qua vao channel cho den khi main bat dau doc. Tuc la main o day quyet dinh nhip, va doi khi dieu do rat tuyet.
Buoc 4: buffered channel — mot queue nho cho ket qua
Bay gio hay bien channel thanh buffered. Vi du, voi 2 ket qua:
package main
import "fmt"
func main() {
tasks := []Task{{1, "Buy milk"}, {2, "Write Go"}, {3, "Sleep"}}
ch := make(chan TaskStat, 2) // buffered, cap=2
for _, t := range tasks {
go func(task Task) {
ch <- calcStat(task) // may not wait until buffer is full
}(t)
}
for i := 0; i < len(tasks); i++ {
stat := <-ch
fmt.Println(stat.ID, stat.TitleSize)
}
}
Hanh vi thay doi nhu the nao? Bay gio hai goroutine co the “ban nhanh”: dua ket qua vao buffer va ket thuc, ke ca khi main chua doc. Nhung lan send thu ba co the bi block neu buffer da day (vi cap=2, trong khi co 3 tac vu).
Mot quan sat quan trong ve thu tu
Thu tu ket qua ve main trong cach lam nay khong duoc dam bao “theo ID”. Channel dam bao FIFO theo thu tu send thuc te, nhung “ai send truoc” lai phu thuoc vao scheduler va toc do chay cua goroutine. Dieu do hoan toan binh thuong: dong thoi gan nhu luon co nghia la ban khong nen gia su thu tu, hoac phai dam bao no o mot noi khac (vi du, sap xep sau khi tong hop — nhung do la logic du lieu, khong phai logic cua channel).
6. Cac loi sai thuong gap khi chon buffered/unbuffered
Loi so 1: “Buffered channel khong bao gio bi block”.
Day la ao tuong pho bien: co cam giac la neu da co buffer thi sender luc nao cung co the “nop hang roi di”. Thuc te buffer co gioi han. Khi len(ch) == cap(ch), send bi block giong het nhu unbuffered, chi la o mot thoi diem khac. Vi loi nay, nguoi ta dat cap=1, gui hai gia tri va ngac nhien vi chuong trinh bi treo.
Loi so 2: “Hay tao buffer that lon de khoi phai nghi ve block”.
Buffer lon thuong bien thanh kho chua van de: no lam mo di su khong dong bo ve toc do, tich luy gia tri, tang su dung bo nho va lam do tre kho du doan hon. Doi khi he thong co ve “on dinh”, cho den khi tai trong that den — va luc do bo nho vuot xa tam tay. Buffer phai la mot quyet dinh co y thuc: chung ta san sang luu gi trong “hang doi cho”.
Loi so 3: xay dung logic dong bo dua vao len(ch).
len(ch) cho biet trang thai “ngay luc nay”, nhung the gioi dong thoi thay doi rat nhanh. Code kieu if len(ch) > 0 { v := <-ch } khong dam bao doc khong block: giua phep kiem tra va lan doc, mot goroutine khac co the da read gia tri. Ket qua la ban van co the bi treo, chi la luc nay con co cam giac minh bi “lua”.
Loi so 4: chon unbuffered trong khi sender can co the ket thuc nhanh.
Doi khi ban co mot goroutine ngan, chi can gui mot ket qua va ket thuc. Neu channel la unbuffered, no co the bi treo o ch <- v neu receiver bi tre. Trong cac truong hop nhu vay, buffer 1 thuong lam hanh vi on dinh hon: goroutine bo gia tri vao va ket thuc, khong tro thanh tin do cua timing.
Loi so 5: “Buffer la de tang toc”, ma khong hieu “chung ta dang giai quyet van de gi”.
Buffer khong lam code cua ban tu dong nhanh hon. No thay doi noi ma cac goroutine doi nhau. Doi khi no tang throughput, doi khi chi lam viec debug kho hon. Neu ban khong the giai thich bang loi noi vi sao minh can buffer, hay bat dau voi unbuffered: no thuong don gian hon, trung thuc hon, va ren ky luat giao tiep.
GO TO FULL VERSION