Intro
While channels are the glue that binds goroutines together, the select
statement is the glue that binds channels together. The select statement is undoubtedly one of the most critical things in a Go program with concurrency.
The select
statement help safely bring channels together with concepts like cancellations, timeout, waiting, and default values.
Concepts
The syntax of select
block is a bit like switch
block. However, unlike switch
blocks where case
statements in a select
block are tested sequentially, in select
block, the execution won’t fall through if none of the criteria are met. That is saying if none of the channels are ready, the entire select statement blocks until one of the channels is ready and the corresponding statements will execute.
1func simpleSelectExample() {2 ch := make(chan any)3
4 start := time.Now()5 go func() {6 time.Sleep(3 * time.Second)7 // close channel after 3 seconds8 close(ch)9 }()10
11 fmt.Printf("Blocking...\n")12 select {13 //blocked until goroutine signals close14 case <-ch:15 fmt.Printf("Unblocked %.2v later!\n", time.Since(start))2 collapsed lines
16 }17}
1# outputs:2Blocking...3Unblocked 3. later!
Delve deeper
TR;TR
- The Go runtime performs a pseudorandom uniform select over the set of case statements, meaning each case statement has an equal chance of being selected.
- Add timeout in the
case
clause to prevent forever blocking. - If we want to process somthing when no channels are ready, have the
default
clause and put theselect
block in a for loop (for-select loop).
1) what if multiple channels have something to read?
Each clause of the select
statement has the equal chance to be selected. Here is an example:
1func selectEqualChanceExample() {2 c1 := make(chan interface{})3 close(c1)4 c2 := make(chan interface{})5 close(c2)6
7 var c1Count, c2Count int8 for i := 1000; i >= 0; i-- {9 select {10 case <-c1:11 c1Count++12 case <-c2:13 c2Count++14 }15 }2 collapsed lines
16 fmt.Printf("c1Count: %d\nc2Count: %d\n", c1Count, c2Count)17}
1# outputs:2c1Count: 5083c2Count: 493
As can be seen from the result, the two clauses roughly have the chance to be executed.
2) what if all channels would never be ready?
Use a timeout clause in select
statement. Here is an example:
1func selectTimeoutExample() {2 now := time.Now()3 ch1 := make(chan any)4
5 select {6 case <-ch1:7 case <-time.After(2 * time.Second):8 fmt.Printf("Timed out after %.2v seconds\n", time.Since(now))9 }10}
1# outputs:2Timed out after 2. seconds
3) what if we want to do something when no channel is ready?
Use default clauses if you want to do something when all channels you are selecting against re blocking. Here is an example:
1func selectDefaultExample() {2 ch := make(chan any)3 counter := 04
5 go func() {6 time.Sleep(4 * time.Second)7 close(ch)8 }()9
10 loop:11 for {12 select {13 case <-ch:14 break loop15 default:6 collapsed lines
16 }17 counter++18 time.Sleep(1 * time.Second)19 }20 fmt.Println("counter:", counter)21}
1# outputs:2counter: 4
To see the full example of code, you can visit: goSelect.