NOTE
2.6 select
What select is, basic usage, random selection, empty select, nil channels, for-select exit, timeouts, quit channels, internals, and references.
This is a historical learning note and may contain outdated or incomplete understanding.
1. What Is It?
- Similar to I/O multiplexing.
- Listen for multiple blocking events. If an event happens, execute that event’s logic.
- If none happens and there is a default event, execute the default logic.
- If none happens and there is no default event, keep blocking.
2. Usage
2.1. Basic Usage
select {
case v1 := <-c1: // receive operation
fmt.Printf("received %v from c1\n", v1)
case v2 := <-c2: // receive operation
fmt.Printf("received %v from c2\n", v1)
case c3 <- 23: // send operation
fmt.Printf("sent %v to c3\n", 23)
default: // default
fmt.Printf("no one was ready to communicate\n")
}
When execution reaches select, the case statements are evaluated in source-code order and only once. The evaluation can result in the following situations:
- Excluding
default, if only one case evaluates successfully, execute the statements in that case. - Excluding
default, if multiple cases evaluate successfully, choose one pseudo-randomly. - If none of the cases other than
defaultevaluates successfully, execute the statements indefault. - If there is no
default, the code block is blocked until one case evaluates successfully; otherwise it remains blocked.
2.2. Randomness
func TestSelect6(t *testing.T) {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
// Output
Sometimes:
from server2
Sometimes:
from server1
2.3. select{} Blocks Forever
func TestSelect2(t *testing.T) {
go func() {
fmt.Println("aaaaaaaaaaa")
}()
select {}
}
// Output
aaaaaaaaaaa
fatal error: all goroutines are asleep - deadlock!
2.4. A nil Channel Blocks Forever
func TestSelect5(t *testing.T) {
var ch chan string
select {
case v := <-ch: // a nil channel blocks forever
fmt.Println("received value", v)
}
}
// Output
fatal error: all goroutines are asleep - deadlock!
2.5. Exit a for-select Loop
Use return or goto.
func TestSelect3(t *testing.T) {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(time.Second * 5)
ch1 <- 1
}()
go func() {
time.Sleep(time.Second * 10)
ch2 <- 2
}()
for {
select {
case data := <-ch1:
fmt.Println(data)
break // break only exits select, not for
case data := <-ch2:
fmt.Println(data)
goto TAG // goto or return can be used
}
fmt.Println("exit select")
}
TAG:
fmt.Println("exit for")
}
// Output
1
exit select
2
exit for
2.6. Set a Timeout for a Request
func main() {
c := requestBaidu()
timeout := time.After(5 * time.Second)
for {
select {
case s := <-c:
fmt.Println(s)
case <-timeout:
fmt.Println("request timed out.")
return
}
}
}
func requestBaidu() chan int {
return make(chan int)
}
// Output
request timed out.
2.7. quit Channel
func TestSelect4(t *testing.T) {
quit := make(chan int)
data := make(chan int)
go func() {
for {
select {
case d := <-data:
fmt.Println("received data:", d)
case <-quit:
fmt.Println("exit")
return
}
}
}()
for i := 0; i < 10; i++ {
data <- i
}
quit <- 3
time.Sleep(time.Minute)
}
3. Internals
3.1. select{}
func TestSelect(t *testing.T) {
select {}
}
go tool compile -S select1_test.go
0x0000 00000 (select2_test.go:5) TEXT "".TestSelect(SB), ABIInternal, $8-8
0x0000 00000 (select2_test.go:5) MOVQ TLS, CX
0x0009 00009 (select2_test.go:5) MOVQ (CX)(TLS*2), CX
0x0010 00016 (select2_test.go:5) CMPQ SP, 16(CX)
0x0014 00020 (select2_test.go:5) JLS 40
0x0016 00022 (select2_test.go:5) SUBQ $8, SP
0x001a 00026 (select2_test.go:5) MOVQ BP, (SP)
0x001e 00030 (select2_test.go:5) LEAQ (SP), BP
0x0022 00034 (select2_test.go:5) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0022 00034 (select2_test.go:5) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0022 00034 (select2_test.go:5) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0022 00034 (select2_test.go:6) PCDATA $0, $0
0x0022 00034 (select2_test.go:6) PCDATA $1, $0
0x0022 00034 (select2_test.go:6) CALL runtime.block(SB)
0x0027 00039 (select2_test.go:6) XCHGL AX, AX
0x0028 00040 (select2_test.go:6) NOP
0x0028 00040 (select2_test.go:5) PCDATA $1, $-1
0x0028 00040 (select2_test.go:5) PCDATA $0, $-1
0x0028 00040 (select2_test.go:5) CALL runtime.morestack_noctxt(SB)
0x002d 00045 (select2_test.go:5) JMP 0
You can see that it calls CALL runtime.block(SB).
3.2. One Case
select {
case v <- ch:
//...
}
// translated to
if v <- ch {
//...
}
3.3. One Case + default
select {
case v <- ch:
//...
default:
//...
}
// translated to
if v <- ch {
//...
} else {
//...
}
3.4. Multiple Cases
func TestSelect4(t *testing.T) {
quit := make(chan int)
data := make(chan int)
go func() {
for {
select {
case d := <-data:
fmt.Println("received data:", d)
case <-quit:
fmt.Println("exit")
return
}
}
}()
for i := 0; i < 10; i++ {
data <- i
}
quit <- 3
time.Sleep(time.Minute)
}
go tool compile -S select1_test.go
0x0128 00296 (select1_test.go:15) PCDATA $0, $8
0x0128 00296 (select1_test.go:15) PCDATA $1, $0
0x0128 00296 (select1_test.go:15) LEAQ ""..autotmp_19+128(SP), DX
0x0130 00304 (select1_test.go:15) PCDATA $0, $0
0x0130 00304 (select1_test.go:15) MOVQ DX, (SP)
0x0134 00308 (select1_test.go:15) PCDATA $0, $8
0x0134 00308 (select1_test.go:15) LEAQ ""..autotmp_20+64(SP), DX
0x0139 00313 (select1_test.go:15) PCDATA $0, $0
0x0139 00313 (select1_test.go:15) MOVQ DX, 8(SP)
0x013e 00318 (select1_test.go:15) MOVQ $2, 16(SP)
0x0147 00327 (select1_test.go:15) CALL runtime.selectgo(SB)
0x014c 00332 (select1_test.go:15) MOVQ 24(SP), AX
It calls runtime.selectgo(SB).
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub