Tìm hiểu về Goroutines – cùng Hìn béo cưa anh Khấc

Ở bài này, chúng ta sẽ cùng làm quen với 1 nhân vật mới: anh Khấc – fullstack developer của công ty A, crush của Hìn béo.

Tóm tắt kiến thức

  • Goroutines là function/method chạy cùng lúc với function/method khác.
  • Goroutines có nhiều lợi ích hơn so với thread:
    • Kích thước nhỏ: khi khởi tạo Goroutines chỉ tốn vài kb trong vùng nhớ stack, có thể resize được. Thread thì tốn ~ 2MB khi khởi tạo, không thể resize.
    • Hàng ngàn Goroutines có thể chạy trong 1 OS thread. Nếu thread này bị block, 1 thread mới được tạo ra. Một vài goroutines ở lại thread cũ để xử lý tiếp, số goroutines còn lại được chuyển qua thread mới để process tiếp. Cái này Go tự xử lý, anh em đọc cho vui, ko cần code gì cả.
    • Xử lý các vấn đề đọc/ghi dữ liệu cùng lúc giữa các vùng nhớ chung đơn giản thông qua channel.
  • Start 1 goroutines bằng cách đặt từ khóa `go` trước method/function.

Chém gió về Goroutines

Anh Khấc là 1 lập trình viên fullstack của công ty A. Anh làm đủ thứ, từ team member, frontend, backend, QA; thậm chí tới cả techlead hay PM nữa.

. Thi thoảng, anh hay rủ Phong đi trà đá, nhâm nhi điếu thuốc lào để bàn chuyện chơi bời, gái gú… à nhầm chuyện coding, chia sẻ kinh nghiệm sống.
Đẹp trai, đa tài, nhà mặt phố, đi làm chỉ vì đam mê, anh Khấc trở thành hình tượng được nhiều chị em trong công ty mơ ước, trong đó có Hìn béo. 

Một ngày đẹp trời, Hìn mặc cái váy xinh đẹp mới mua tới chỗ anh Khấc: 
– Anh Khấc, em đang học code Go. Có vài thắc mắc liên quan tới Goroutines, anh giải thích giúp em nhé.

Anh Khấc mỉm cười:
– Được. Nói anh nghe em biết gì về Goroutines rồi?

– Theo em được biết thì Goroutines là công cụ của Golang để thực hiện concurrency. Goroutines giống như thread, nhưng nhỏ hơn nhiều lần. Hàng ngàn goroutines có thể nằm trong 1 thread hệ thống.

– Nếu thread này bị block thì sao?

– Nếu thread này bị block, 1 thread mới được tạo ra. Một vài goroutines ở lại thread cũ để xử lý tiếp (ví dụ đợi người dùng nhập dữ liệu, sleep), các goroutines còn lại được chuyển sang thread mới để tiếp tục xử lý.

– Khá lắm. Thế em có thắc mắc gì?

– Em code thử 1 đoạn, nhưng chạy không đúng như mong muốn 

– Viết thử lên bảng anh xem.

Nói đoạn, anh Khấc thò tay vào túi quần, đưa cho Hìn bút của anh – cây bút dạ mà anh hay dùng, không phải bút kia , bảo Hìn viết lên bảng.

package main
import (
"fmt"
)
func hienCoXinhGaiKhong() {
fmt.Println("Hien rat xinh gai")
}
func main() {
go hienCoXinhGaiKhong()
fmt.Println("Good bye")
}
output
Good bye

view rawhin_xinh_gai.go hosted with ❤ by GitHub

https://play.golang.org/p/LZMO0L1DGJR

— Đây anh. Em muốn in ra ngoài output như sau

1
2
Hien rat xinh gai
Good bye

Nhưng chạy kiểu gì cũng chỉ in ra Good bye mà không in ra dòng Hien rat xinh gai

— À vì Go nó biết nhận diện sự thật. Nếu em nói sai thì nó sẽ không chạy đó

— Đù má anh 

— Anh đùa thôi, do em chưa hiểu kĩ về cách hoạt động của Goroutines đó.

— Là như nào hả anh?

— Em cần nắm được hai điểm: Thứ nhất: khi em sử dụng goroutines để gọi một hàm, thì hàm đó sẽ được return ngay lập tức. Thứ hai: chương trình luôn có một goroutines chính, gọi là main goroutines. Khi goroutines này bị terminated thì toàn bộ chương trình sẽ dừng lại. Các goroutines khác cũng sẽ bị terminated theo 

— Như vậy thì goroutines chạy hàm hienCoXinhGaiKhong bị terminated do goroutines chính bị terminated?

— Đúng vại. Bây giờ em thử cho hàm main sleep 1s thử xem

package main
import (
"fmt"
"time"
)
func hienCoXinhGaiKhong() {
fmt.Println("Hien rat xinh gai")
}
func main() {
go hienCoXinhGaiKhong()
time.Sleep(1 * time.Second)
fmt.Println("Good bye")
}
output
Hien rat xinh gai
Good bye

view rawhin_xinh_gai_with_sleep.go hosted with ❤ by GitHub

https://play.golang.org/p/hrb4jycS3dj

— Oh, em thấy in ra kết quả rồi nè. Anh Khấc đẹp trai, à nhầm giỏi quá. Hihi 

— Bây giờ anh lấy 1 ví dụ, em thử đoán xem kết quả sẽ như thế nào nhé

package main
import (
"fmt"
"time"
)
func test1(){
time.Sleep(100 * time.Millisecond)
fmt.Println("Trứng rán cần mỡ")
time.Sleep(100 * time.Millisecond)
fmt.Println("Yêu không cần cớ")
}
func test2(){
time.Sleep(150 * time.Millisecond)
fmt.Println("Bắp cần bơ")
time.Sleep(150 * time.Millisecond)
fmt.Println("Cần cậu cơ")
}
func main() {
go test1()
go test2()
time.Sleep(2 * time.Second)
fmt.Println("Good bye")
}

view rawvi_du_anh_khac.go hosted with ❤ by GitHub

— Em đoán kết quả sẽ là

1
2
3
4
5
Trứng rán cần mỡ
Bắp cần bơ
Yêu không cần cớ
Cần anh Khấc cơ
Good bye

— Đúng rồi, Hìn đốm lưỡi, à nhầm, thông minh quá. Để anh giải thích kĩ hơn nhé

Sơ đồ chạy

** đoạn này anh Khấc giải thích cho Hìn béo. Sau này tớ sẽ sửa lại cái sơ đồ và giải thích kĩ hơn. Tại thời điểm viết bài tớ lười vẽ sơ đồ quá :)) **

— Oh. Em hiểu hơn về Goroutines rồi. Nhưng mà cứ dùng sleep nghe có vẻ không ổn anh nhỉ? Chương trình chạy sẽ bị chậm và không được tối ưu lắm.

— Ừ. Viết thế này để em hiểu hơn về Goroutines thôi. Còn nhiều thứ hay ho về concurrency trong Go lắm: channel, wait group, deadlock, mutex,… Em dùng những thứ này sẽ kiểm soát được Goroutines mà không cần sleep.

— Nghe thú vị nhỉ. Dạy em mấy cái đấy đi.

— Ừ, từ từ để thằng Phong nó bịa content viết bài đã nhé 

Đã có bài tiếp ở đây:

Tìm hiểu và xử lý race condition bằng atomic, mutex cùng Hìn béo

Lời cảm ơn

Cảm ơn Meo đã giúp anh review lại bài viết, bổ sung ý tưởng cho bài. Khi nào có dịp, anh mời uống trà sữa nhé :))

Cảm ơn chị Hìn béo đã cho thuê bản quyền hình ảnh để viết bài với 1 cốc Soya 

Hi vọng anh Khấc cũng sẽ cho em sử dụng hình ảnh. Khi nào Soya mở lại, em mời anh nhé ❤

Cảm ơn bạn đã bỏ thời gian để đọc tới đây. Hi vọng bài viết có thể có ích với bạn. Nếu trong bài có sai sót gì, hãy comment cho tớ biết với nhé ^^.