NOTE

2.6 select

What select is, basic usage, random selection, empty select, nil channels, for-select exit, timeouts, quit channels, internals, and references.

GoCreated Updated 1 min readhistorical

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:

  1. Excluding default, if only one case evaluates successfully, execute the statements in that case.
  2. Excluding default, if multiple cases evaluate successfully, choose one pseudo-randomly.
  3. If none of the cases other than default evaluates successfully, execute the statements in default.
  4. 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).

4. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub