GO语言学习笔记-Select

原文

什么是Select

select语法用来从多个读/写的channel中选择一个,如果没有任何channel就绪select语句将被阻塞。如果多个就绪,则随机选择一个。语法和switch类似,除了case后面跟随的是channel。

例子

package main

import (  
    "fmt"
    "time"
)

func server1(ch chan string) {  
    time.Sleep(6 * time.Second)
    ch <- "from server1"
}
func server2(ch chan string) {  
    time.Sleep(3 * time.Second)
    ch <- "from server2"

}
func main() {  
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    select {
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}

上面的代码中server1在6秒钟后向ch写入数据,而server2则3秒钟后写入。main函数中启动这2个goroutine,然后程序阻塞在select处,直到满足一个case的条件。所以程序等待3秒钟后会输出from server2后结束。

select实践

假设我们有一个重要的程序需要尽快的返回数据给用户,而数据库是分布式的并且处于世界不同的地方。上面例子中的server1和server2就是这样,响应速度取决于每个服务器的负载和网络延迟,我们向所有服务器发请求,然后使用select来选择接受哪个服务的响应。除了第一个响应外其他的将被忽略,这样我们就可以在多个服务器中选择响应最快的那个结果返回给用户了。

默认case

select中的默认case将在没有任何其他case满足的情况下被执行,这通常用来阻止select语句阻塞。

package main

import (  
    "fmt"
    "time"
)

func process(ch chan string) {  
    time.Sleep(10500 * time.Millisecond)
    ch <- "process successful"
}

func main() {  
    ch := make(chan string)
    go process(ch)
    for {
        time.Sleep(1000 * time.Millisecond)
        select {
        case v := <-ch:
            fmt.Println("received value: ", v)
            return
        default:
            fmt.Println("no value received")
        }
    }

}

上述代码中,process函数等待10.5秒后向ch写入process successful,执行process的goroutine后程序进入死循环,每秒钟尝试一次读取channel中的数据。在10.5秒钟之前程序会进入default分支,之后进入到case v := <-ch分支并跳出循环,所以程序输出如下:

no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
no value received  
received value:  process successful  

死锁和默认case

package main

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    }
}

上面的代码我们创建了一个channel并尝试在select中读取数据,因为没有任何goroutine向其写入数据,select将永远阻塞下去导致死锁,报错如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox416567824/main.go:6 +0x80

如果提供了默认case,程序将不会死锁:

package main

import "fmt"

func main() {  
    ch := make(chan string)
    select {
    case <-ch:
    default:
        fmt.Println("default case executed")
    }
}

程序输出如下:

default case executed

类似的,如果是一个空channel(roy注:之前学channel时候说过,channel没使用make创建时的值是nil),默认case也会被执行:

package main

import "fmt"

func main() {  
    var ch chan string
    select {
    case v := <-ch:
        fmt.Println("received value", v)
    default:
        fmt.Println("default case executed")

    }
}

上述代码中我们尝试从nil的channel中读取数据,如果没有default,程序将死锁。

## 随机选择 当select中多个case都满足时,将随即选择一个:

package main

import (  
    "fmt"
    "time"
)

func server1(ch chan string) {  
    ch <- "from server1"
}
func server2(ch chan string) {  
    ch <- "from server2"

}
func main() {  
    output1 := make(chan string)
    output2 := make(chan string)
    go server1(output1)
    go server2(output2)
    time.Sleep(1 * time.Second)
    select {
    case s1 := <-output1:
        fmt.Println(s1)
    case s2 := <-output2:
        fmt.Println(s2)
    }
}

上述代码中,由于main函数中等待了1秒钟,所以server1server2都有充足的时间执行完毕,即2个case都满足。如果你多次运行这个程序,输出将在from server1from server2随机选择。

空select

package main

func main() {  
    select {}
}

你觉得上面的程序输出什么?

我们知道select将被阻塞直到一个case满足,但上面的select没有任何case,因此程序将死锁,报错如下:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:  
main.main()  
    /tmp/sandbox299546399/main.go:4 +0x20