Mở đầu
- Lập trình concurrency luôn là một thứ làm các gopher tự hào so với các ngôn ngữ khác. Các ngôn ngữ phổ biến như JAVA, C# implements concurrency bằng việc sử dụng thread, còn GO thì sử dụng 2 built-in features - goroutine và channel. Chúng giúp việc tiếp cận hay quản lý các công việc đồng thời trở nên dể dàng và đỡ tốn chi phí hơn rất nhiều.
- Goroutines bản chất là các hàm (function) hay method được thực thi một các độc lập và đồng thời nhưng vẫn có thể kết nối với nhau. Và channel chính là kênh giao tiếp giữa các goroutine đó.
Channel
- Channel cung cấp một cách để các goroutines giao tiếp với nhau và đồng bộ hóa việc thực thi của chúng.
- Nói đến giao tiếp, thì phải có 2 chiều - chiều gửi và chiều nhận. Channel cũng như thế, goroutine hoàn toàn có thể gửi đến dữ liệu đến channel và lấy dữ liệu từ channel.
- Channel được phân thành 2 loại: buffer channel and unbuffered channel. Đối với unbuffer channel, khi một goroutine A gửi dữ liệu đến thì nó sẽ tiến hành block goroutine A lại cho đến khi có go routine bất kỳ đến lấy dữ liệu. Và ngược lại, buffer channel sẽ nhẹ nhàng hơn, không có yêu cầu khắt khe gì cả, nó được mang trong mình một sức chứa(capicity) nhất định và không cần bất kỳ receiver goroutine nào. Tuy nhiên, khi capicity đến mức giới hạn thì nó cũng cần goroutines đến lấy dữ liệu ra.
Play with unbuffered channel
- From main goroutine, gửi dữ liệu đến channel without receiver goroutine
Chương trình bị crash(terminated) và thông báo lỗi bởi vì main goroutine bị block trong một khoảng thời gian.func main() { /* Without receiver goroutine from main goroutine */ chanelAtMainRoutine := make(chan int) chanelAtMainRoutine <- 5 } result: fatal error: all goroutines are asleep - deadlock!
- From anonymous goroutine, gửi dữ liệu đến channel without receiver goroutine
Việc chương trình ko bị terminated or crash ở đây là hoàn toàn chính xác mặc dù channel vẫn chưa có một goroutine nào đến lấy dữ liệu. Channel thực chất chỉ block anonymous goroutine và main goroutine vẫn tiếp chạy và in ra kết quả "completing ...."func main() { /* Without receiver from anonymous goroutine */ chanelAtMainRoutine := make(chan int) go func() { chanelAtMainRoutine <- 5 }() fmt.Println("Completing ...") } result: Completing ...
- From main goroutine, gửi dữ liệu đến channel with receiver goroutine
Recevier goroutine xuất hiện và main goroutine ko bị lock nữa. Tuy nhiên, nếu trong quá trình chạy func main nhiều lần ta sẽ thấy dòng "Complete getting value from channel ..." sẽ có lúc ko được in ra màn hình console bởi vì ngay tại thời điểm câu lệnh "<-chanelAtMainRoutine" được thực thi xong, thưc chất golang runtime schedule switch sang main goroutine và nếu việc chuyển đổi này đủ nhanh, sẽ ko có câu lệnh nào kịp thực thi ngay sau đó. Để kiểm chứng điều này, tôi sẽ tiến hành sleep một giây tai recevier goroutine.func main() { /* With receiver from main goroutine */ chanelAtMainRoutine := make(chan int) go func() { fmt.Println("Start getting value from channel ...") <-chanelAtMainRoutine fmt.Println("Complete getting value from channel ...") }() chanelAtMainRoutine <- 5 fmt.Println("Completing ...") } result: Start getting value from channel ... Complete getting value from channel ... Completing ...
Increase time at recevier goroutinefunc main() { /* Increase time at receiver goroutine */ chanelAtMainRoutine := make(chan int) go doBigJob(chanelAtMainRoutine) chanelAtMainRoutine <- 5 fmt.Println("Completing ...") } func doBigJob(ch <-chan int) { fmt.Println("Start getting value from channel ...") <-ch time.Sleep(time.Second * 1) fmt.Println("Complete getting value from channel ...") } result: Start getting value from channel ... // Cannot see "Complete getting value ..." any more Completing ...
Tạm kết
- Về nguyên lý , unbuffered channel hoạt động giống như môn bóng bàn. Khi bóng giao đi thì phải có người đỡ. Ở ví dụ trên chúng ta có thể chuyển sang dùng buffer channel , để câu lệnh fmt.Println("Complete getting value from channel ...") được thực thi ngay khi dữ liệu của channel được lấy ra.
- Source code
- Hẹn các bạn đón xem phần tiếp theo. Chúng ta sẽ đi tìm hiểu thêm về buffered channel và các usecase của nó.