[Golang] – Tìm hiểu về select cùng các em gái “sugar baby”

Một phần của series “Đại ca Phong học Golang


— Anh Tèoo, đi đâu đấy?

— À, anh đi phỏng vấn ấy mà. Đi không Hằng?

— Cóóó. Em mới học được mấy câu về select. Em hỏi thử ứng viên luôn nghen.

— Vào việccc

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

  • select trong Go thường được dùng trong việc gửi/nhận giữa nhiều channel (channel operation). Anh em nhìn sẽ thấy có mùi giống switch case.
  • Mệnh đề select sẽ block chương trình lại cho tới khi có 1 operation ready.
  • Nếu có nhiều hơn 1 operation ready đồng thời -> sẽ chọn random
  • Nếu select không có mệnh đề nào -> panic
  • Nếu tất cả go routine trong mệnh đề select bị block -> panic
  • Ứng dụng trong case:
    • Bạn có 2 con server giống hệt nhau (gọi là A và B)
    • Khi user gửi req lên API của bạn, bạn gửi req đồng thời đến cả 2 con A và B kia
    • Con nào trả về trước thì lấy kết quả trả cho người dùng trước

Chém

Giới thiệu qua về ứng viên hôm nay: đó là 1 em gái với cái tên rất kêu: Tủm.

Nàng có mái tóc dài, cao 1m7, nặng 70kg; với 2 năm kinh nghiệm làm backend.

— Chào em. Anh là Tèo, Tech lead của công ty A. Hôm nay phỏng vấn cùng anh có Hằng là 1senior dev của team. Em có thể giới thiệu đôi chút về bản thân mình không?

— Dạ chào anh chị, em tên là Tủm. Em có 2 năm kinh nghiệm làm trong ngành công nghiệp không khói này. Ngôn ngữ sử dụng chính của em là Golang.

— Oh. Vậy là có 2 năm làm Golang.

— Dạ, em có 2 năm kinh nghiệm về Golang, trong đó 1 năm rưỡi là đi học, nửa năm là làm.

— Ok. Vậy bọn anh hỏi mấy câu về Golang. Hằng vào việc đi.

— Dạ vâng. Em có dùng select bao giờ chưa?

— Dạ có. Em thường dùng select khi cần chọn ra 1 go routine sẵn sàng trong 1 đống go routine.

— Em thử lấy ví dụ chị xem

— Ví dụ em làm dịch vụ “Cung cấp sugar baby”. Để phục vụ được nhiều khách hàng nhất, em tạo 2 cơ sở: A và B.

Mỗi khi có khách hàng tới, em gọi điện cho cả 2 cơ sở A và B chở baby tới. Em nào tới trước thì vào phục vụ khách luôn; em nào tới sau thì về.

Em code như sau:

package main
import (
"fmt"
"math/rand"
"time"
)
func GoiHangA(ch chan string) {
randTime := rand.Intn(5)
fmt.Printf("Mất %v giây để chờ hàng của cơ sở A tới\n", randTime)
time.Sleep(time.Duration(randTime) * time.Second)
ch <-< span> "Hàng từ cơ sở A"
}
func GoiHangB(ch chan string) {
randTime := rand.Intn(5)
fmt.Printf("Mất %v giây để chờ hàng của cơ sở B tới\n", randTime)
time.Sleep(time.Duration(randTime) * time.Second)
ch <-< span> "Hàng từ cơ sở B"
}
func main() {
coSoA := make(chan string)
coSoB := make(chan string)
go GoiHangA(coSoA)
go GoiHangB(coSoB)
var selected string
select {
case hangA := <-< span>coSoA:
selected = hangA
case hangB := <-< span>coSoB:
selected = hangB
}
fmt.Printf("Đã chọn hàng: %v\n", selected)
}

view rawgoihang.go hosted with ❤ by GitHub

Bởi vì cửa hàng A và B qua cơ sở chính sẽ tuỳ thuộc vào độ tắc đường, nên kết quả sẽ random

1
2
3
4
5
6
7
8
9
10
Mất 1 giây để chờ hàng của cơ sở B tới
Mất 2 giây để chờ hàng của cơ sở A tới
 
Đã chọn hàng: Hàng từ cơ sở B
 
----
Mất 1 giây để chờ hàng của cơ sở A tới
Mất 3 giây để chờ hàng của cơ sở B tới
 
Đã chọn hàng: Hàng từ cơ sở A

— Tốt lắm. Vậy giả sử chị bỏ phần gọi hàm go GoiHangA(coSoA) và go GoiHangB(coSoB) ở dòng 29, 30 đi thì điều gì xảy ra?

— Thì chị ăn quả combo panic + deadlock vào mồm chứ sao. Hỏi gì mà ngu… à nhầm, ý em là nó bị deadlock. Hihi

1
2
3
4
5
fatal error: all goroutines are asleep - deadlock!
 
goroutine 1 [select]:
main.main()
        /Users/apple/go/src/tum/main.go:34 +0xf0

— Thế em giải thích cho chị tại sao lại bị panic và làm thế nào để tránh panic không?

— À, thì do mệnh đề select sẽ đợi cho tới khi có 1 goroutine trong các case ready thì thôi. Ở đây bỏ 2 hàm gọi kia đi thì 2 goroutine đều bị block do sử dụng unbuffered channel. Ko có case nào ready thì giống như đến tuổi lấy chồng rồi mà chưa có người yêu; hay gọi là bị ế đó. Hihi

— Ơ con này, mày xiên xỏ gì đấy

— Thôi thôi, đang phỏng vấn mà. Thế có cách nào chống panic không? – anh Tèo hỏi

— À có thể dùng default ạ. Nếu ko có goroutine nào ready thì default case sẽ chạy

package main
import (
"fmt"
"math/rand"
"time"
)
func GoiHangA(ch chan string) {
randTime := rand.Intn(5)
fmt.Printf("Mất %v giây để chờ hàng của cơ sở A tới\n", randTime)
time.Sleep(time.Duration(randTime) * time.Second)
ch <-< span> "Hàng từ cơ sở A"
}
func GoiHangB(ch chan string) {
randTime := rand.Intn(5)
fmt.Printf("Mất %v giây để chờ hàng của cơ sở B tới\n", randTime)
time.Sleep(time.Duration(randTime) * time.Second)
ch <-< span> "Hàng từ cơ sở B"
}
func main() {
coSoA := make(chan string)
coSoB := make(chan string)
//go GoiHangA(coSoA)
//go GoiHangB(coSoB)
var selected string
select {
case hangA := <-< span>coSoA:
selected = hangA
case hangB := <-< span>coSoB:
selected = hangB
default:
selected = "Không có hàng"
}
fmt.Printf("Đã chọn hàng: %v\n", selected)
}

view rawgoihang_2.go hosted with ❤ by GitHub

— Khá lắm. Vậy giả sử mệnh đề select bị empty thì sao em?

— Ơ, em chưa biết ạ. Thế bị sao hả anh?

— Nó cũng bị panic em ạ

1
2
3
fatal error: all goroutines are asleep - deadlock!
 
goroutine 1 [select (no cases)]:

— Oh ra là vậy.

— Phần select em nắm được thế là cũng okie rồi. Anh sẽ hỏi thêm 1 số câu về defer, panic, recover trong bài tới nhé.


Cảm ơn bạn, vì đã đọc bài ^^