Golang教程:(二十四)Select

原文地址:https://golangbot.com/select/

欢迎来到第二十四节。

什么是select?

select模式是用来从多输入/输出通道中进行选择的一种操作。select模式下会一直阻塞直到至少一个可读/写操作就绪,如果多个读写操作就绪,那么久随机从中选择一个。语法有点类似switch除了每个case选择都是一个通道操作。为了更好理解上代码:

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)
    }
}

在playground运行

上面例子中,server1函数sleep 6秒之后往ch写数据,server2函数sleep 3秒后往ch写数据。main函数中创建了server1和server2两个协程,然后select模式将阻塞主协程直到其中一个case满足,在上面的例子中server1会休眠6秒而server2会休眠3秒,所以3秒后select模式会收到server2中ch通道的写入事件,打印出:

from server2

然后程序运行结束。

select的实际应用

让我们假设有一个关键性的应用,需要高响应回复用户,这个应用的数据库是分别存放在世界各地不同的服务器上,就好像我们是server1和serve2一样,服务器相应的时间是根据各自的网络延时出现不同的时延,我们发送请求给各个服务器,然后通过使用select模式就可以帮我们把最先相应的服务器筛选出来,而忽略其他响应速度慢的服务器。这样我们就可以尽快的把结果返回给用户了:)

Default 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")
        }
    }


}

在playground运行

在上面的例子中,process函数会sleep 10.5秒后往ch中写入数据。main中启动process协程后,定义了一个循环,每次sleep 1秒,在之后的10.5秒钟select会先查看case v:= <-ch:,这时是不会就绪的,所以转到default执行print,直到10.5秒之后ch通道中写入了数据case v才会执行,然后输出结果跳出循环。结果打印如下:

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  

Deadlock and default case

package main


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

在playground运行

上面例子中我们创建了一个通道ch,我们尝试从中读取数据但是这种情况永远不会发生,这时就会出现死锁现象。程序会打印报错:

fatal error: all goroutines are asleep - deadlock!

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

如果我们加上default case的话,死锁的情况就不会发生:

package main


import "fmt"


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

在playground运行

结果打印

default case executed  

类似的 ,当select中出现nil通道的话,default 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")


    }
}

随机选择

在一个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)
    }
}
在playground运行

在上面的代码中,函数server1和server2都会往通道中写入数据,在 main函数中当启动两个协程后,sleep 1秒,这时select会接收到case s1和case s2都是就绪状态,会随机从中选择一个进行执行。具体哪个执行完全是取决随机数的。

Gotcha - 空的select

package main


func main() {  
    select {}
}

在playground运行

你认为上面会出现什么情况?我们知道select会阻塞住直到有其中一个case得到满足。上面的情况没有一个case所以会出现死锁的情况,打印如下:
fatal error: all goroutines are asleep - deadlock!

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

这节就到这里,愉快一天!







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值