Trong ngôn ngữ Go, câu lệnh select
giống như câu lệnh Switch, nhưng trong câu lệnh select
, câu lệnh case
liên quan đến việc trao đổi thông tin ví dụ như các hoạt động nhận và gửi trên channel. Thông thường mỗi câu lệnh case
sẽ được đọc thử từ channel. Khi một case bất kỳ sẵn sàng (channel được đọc), thì câu lệnh kết hợp với case
đó được chạy. Nếu nhiều case
sẵn sàng, nó sẽ lựa chọn ngẫu nhiên một. Bạn có thể có case
mặc định được chạy nếu không có case
nào sẵn sàng.
Cú pháp
select { case SendOrReceive1: // Câu lệnh case SendOrReceive2: // Câu lệnh case SendOrReceive3: // Câu lệnh ....... default: // Câu lệnh
Hãy cùng xem ví dụ sau đây
package main import "fmt" import "time" // Đẩy dữ liệu cho channel và chờ 4 giây func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } // Đẩy dữ liệu cho channel và chờ 2 giây func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { // Tạo các biến channel để truyền giá trị string chan1 := make(chan string) chan2 := make(chan string) // Gọi các goroutine cùng với các biến channel go data1(chan1) go data2(chan2) //Cả hai câu lệnh case đều đợi dữ liệu trong chan1 hoặc chan2 //chan2 nhân dữ liệu trước do nó chỉ chờ 2 giây trong data2(). //Nên case thứ 2 sẽ chạy và thoát khỏi khối select select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) } }
Ở đây câu lệnh select
đợi dữ liệu sẵn sàng trong channel bất kỳ. data2()
thêm dữ liệu vào channel sau khi tạm dừng 2 giây do đó câu lệnh case thứ 2 được chạy.
Kết quả
from data2()
Case mặc định
case
mặc định trong câu lệnh select
được chạy khi không có case
nào sẵn sàng. Nó thường được sử dụng để ngăn việc câu lệnh select
chặn chương trình chạy.
Hãy cùng thêm case default
(mặc định) cho chương trình bên trên và cùng xem kết quả chạy. Ở đây khi chạy đến đoạn select
, nếu không có channel nào có dữ liệu ở trạng thái sẵn sàng, nó sẽ chạy đoạn code mặc định mà sẽ không đợi cho đến khi một kênh bất kỳ có dữ liệu
package main import "fmt" import "time" // Đẩy dữ liệu cho channel và chờ 4 giây func data1(ch chan string) { time.Sleep(4 * time.Second) ch <- "from data1()" } // Đẩy dữ liệu cho channel và chờ 2 giây func data2(ch chan string) { time.Sleep(2 * time.Second) ch <- "from data2()" } func main() { // Tạo các biến channel để truyền giá trị string chan1 := make(chan string) chan2 := make(chan string) // Gọi các goroutine cùng với các biến channel go data1(chan1) go data2(chan2) // Cả hai câu lệnh case kiểm tra dữ liệu trong chan1 or chan2. // Nhưng dữ liệu không sẵn sàng (Cả 2 routines đều tạm dừng 2 giây và 4 giây) // Nên đoạn code trong default sẽ được chạy mà không chờ dữ liệu trong các channel. select { case x := <-chan1: fmt.Println(x) case y := <-chan2: fmt.Println(y) default: fmt.Println("Default case executed") } }
Kết quả
Default case executed
Đợi vô hạn
Nếu câu lệnh select
không có bất kỳ câu lệnh case
nào thì câu lệnh select
sẽ đợi vô hạn và kết quả sẽ dẫn đến một deadlock trong chương trình
Cú pháp như sau
select{}
Hãy cùng xem ví dụ sau
package main // Hàm main func main() { // Câu lệnh Select // không có case select{ } }
Kết quả
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select (no cases)]: main.main() /home/runner/main.go:9 +0x20 exit status 2
Deadlock
Câu lệnh select
bị chặn khi không có một câu lệnh case
nào ở trạng thái sẵn sàng và câu lệnh select
không chứa bất kỳ case
mặc định nào, thì câu lệnh select
sẽ bị chặn cho tới khi ít nhất có một case
hoặc việc trao đổi thông tin được tiến hành
Hãy cùng xem ví dụ sau
package main // Hàm main func main() { // tạo channel mychannel:= make(chan int) // channel không sẵn sàng // và không có case mặc định select{ case <- mychannel: } }
Trong chương trình trên chúng ta tạo một channel và thử đọc từ channel này trong select
. Câu lệnh select
sẽ chờ mãi mãi bởi vì không có Goroutine nào ghi vào channel và do đó kết quả là deadlock giống với trường hợp bên trên
Kết quả
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive]: main.main() /home/runner/main.go:14 +0x52 exit status 2
Nhiều case cùng ở trạng thái sẵn sàng
Trong câu lệnh select
, nếu nhiều case
cùng ở trạng thái sẵn sàng để xử lý, thì một trong những case
đó có thể được lựa chọn ngẫu nhiên
Hãy cùng xem ví dụ sau đây
package main import "fmt" // Hàm 1 func portal1(channel1 chan string){ for i := 0; i <= 3; i++{ channel1 <- "Welcome to channel 1" } } // Hàm 2 func portal2(channel2 chan string){ channel2 <- "Welcome to channel 2" } // Hàm main func main() { // Tạo các channel R1:= make(chan string) R2:= make(chan string) // Gọi hàm 1 và // hàm 2 trong goroutine go portal1(R1) go portal2(R2) // lựa chọn select // của trường hợp này là ngẫu nhiên select{ case op1:= <- R1: fmt.Println(op1) case op2:= <- R2: fmt.Println(op2) } }
Kết quả
Welcome to channel 2