Vài điều hay ho về select trong golang
Linh tinh về select Trong go thì cách dùng select cơ bản nhất sẽ để điều khiển read write vào các channel. Nắm rõ cách sử dụng select là vô cùng cần thiết cho intermediate gopher. Mô hình sử dụng select thì thông thường sẽ là: select { case <channel ...
Linh tinh về select
Trong go thì cách dùng select cơ bản nhất sẽ để điều khiển read write vào các channel. Nắm rõ cách sử dụng select là vô cùng cần thiết cho intermediate gopher. Mô hình sử dụng select thì thông thường sẽ là:
1 2 3 4 5 6 7 8 9 10 |
select { case <channel operation>: .... case <channel operation>: .... default: .... } |
Đọc từ channel
1 2 3 4 5 6 7 8 |
func CanWeRead(ch <-chan int) { select { case <-ch: .... // yes, we can read } } |
Tuy nhiên có 1 điểm cần chú ý ở đây là : nếu không có default thì xử lý sẽ bị block
1 2 3 4 5 6 7 8 9 10 |
func CanWeRead(ch <-chan int) { select { case <-ch: .... // yes, we can read default: .... // no, we could not read } } |
Đoạn code trên chính là mô tả dễ hiểu nhất cho việc hiểu thế nào là “non blocking IO”
Để sử dụng giá trị lấy ra từ channel thì có thể viết như dưới đây:
1 2 3 4 5 6 7 8 9 10 |
func CanWeRead(ch <-chan int) { select { case v, ok := <-ch: if !ok { // channel is closed ... } } } |
Viết vào channel
1 2 3 4 5 6 7 8 9 10 |
func CanWeWrite(ch chan int) { select { case ch <- 1: ... // yes, we can write default: .... // no, we could not write } } |
Tương tự như read, default dành cho non-blocking writingwriting
Quiz time
Có 2 câu hỏi thú vị được đặt ra về việc đọc / ghi vào channel
Quiz 1: Đọc / ghi vào nil channel thì chuyện gì sẽ xảy ra?
1 2 3 4 |
var ch chan int <- ch // ch chưa được initialize nên ch = nil |
1 2 3 4 5 |
- 1. panic ? - 2. block ? - 3. other ? |
Câu trả lời là 2, với nil channel thì cả read và write sẽ đều bị block. Việc này sẽ dẫn đến một cách dùng khá hay là việc thay đổi trạng thái block thông qua việc gán nil, ví dụ như trong trường hợp dưới đây thì chúng ta có thể điều khiển việc xử lý sẽ chui vào case 1 hay case 2 thông qua việc gán nil (hoặc ngược lại, gán giá trị)
1 2 3 4 5 6 7 8 |
select { case <- ch1: ... case <- ch2: ch1 = nil // disable previous case } |
Quiz 2: Đọc / ghi vào closed channel thì chuyện gì sẽ xảy ra?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
time.AfterFunc(5 * time.Second, func() { close(done) }) for { select { case <- done: return default: // } } |
1 2 3 4 5 |
- 1. panic ? - 2. block ? - 3. other ? |
Câu trả lời là 3: done channel sẽ bị block khi đọc bởi không có data, và khi close thì việc block sẽ được vô hiệu hoá và chúng ta có thể return xử lý ngay lập tức
Sử dụng reflect với select
Đôi khi cái bạn muốn select từ đó là không cố định, hoặc là bạn muốn select N chan cùng một lúc, khi đó thì syntax của go sẽ không support việc đó. Khi đó bạn phải hack bằng cách sử dụng reflection trong go. Điều đó được thể hiện trong ví dụ dưới đây:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package main import ( "fmt" "math/rand" "reflect" "sync" "time" ) func multpleSelect(chans []chan bool) (int, bool, bool) { cases := make([]reflect.SelectCase, len(chans)) for i, ch := range chans { cases[i] = reflect.SelectCase{ Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch), } } i, v, ok := reflect.Select(cases) return i, v.Interface().(bool), ok } func main() { rand.Seed(time.Now().UnixNano()) chans := make([]chan bool, 100) for i := 0; i < 100; i++ { chans[i] = make(chan bool) } var wg sync.WaitGroup go func() { wg.Add(1) if ch, v, ok := multpleSelect(chans); ok { fmt.Printf("I am chan-%v, value is %v
", ch, v) } wg.Done() }() chans[rand.Int() % len(chans)] <- true wg.Wait() } |
Techtalk via kipalog