Go的Select语句

什么是Select

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

Run in playground

在上面的程序中,server1的函数休眠 6 秒,然后将文本从 server1写入通道chserver2的函数休眠 3 秒,然后将 server2写入通道。

main 函数调用 go Goroutinesserver1server2

到达select语句。该select语句将阻塞,直到其中一个case准备就绪为止。在上面的程序中, Goroutineserver1在 6 秒后写入通道 output1,而 Goroutineserver2在 3 秒后写入通道output2。因此 select 语句将阻塞 3 秒,并等待 Goroutine server2写入通道。3秒后,程序打印

from server2  

然后将终止。

Select的实际使用

将上述程序中的函数命名为server1server2 的原因是为了说明 select 的实际用法。

假设我们有一个关键任务应用程序,我们需要尽快将输出返回给用户。该应用程序的数据库被复制并存储在世界各地的不同服务器中。假设功能server1server2实际上正在与 2 个这样的服务器进行通信。每个服务器的响应时间取决于每个服务器的负载和网络延迟。我们将请求发送到两个服务器,然后使用该select语句在相应的通道上等待响应。首先响应的服务器由 select 分发,其他响应将被忽略。这样我们就可以将相同的请求发送到多个服务器,并将最快的响应返回给用户。

默认情况

select当其他情况都没有准备好时,将执行语句中的默认情况。这通常用于防止 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")
        }
    }

}

Run in playground

在上面的程序中,process的函数休眠 10.5 秒,然后写入process successful通道ch。该函数在第 15 行同时被调用

并发调用processGoroutine 后,主 Goroutine 中会启动无限 for 循环。无限循环在每次迭代开始时休眠 1000 毫秒(1 秒),然后执行Select操作。在前 10500 毫秒内,select 语句的第一种情况即case v := <-ch:不会准备好,因为processGoroutine 仅在 10500 毫秒后才会写入ch通道。因此,该default将在这段时间内执行,并且程序将打印no value received10 次。

10.5 秒后,processGoroutine 写入process successful。现在将执行 select 语句的第一个 case,程序将打印 received value: process successful,然后终止。该程序将输出,

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  

死锁和默认情况

package main

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

Run in playground

在上面的程序中,我们创建了一个ch通道。 我们尝试从第 16行 select 内的读取通道。 select 语句将永远阻塞,因为没有其他 Goroutine 写入该通道,因此将导致死锁。该程序将在运行时出现恐慌并显示以下消息,

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:  
main.main()  
    /tmp/sandbox627739431/prog.go:6 +0x4d

如果存在默认情况,则不会发生这种死锁,因为当没有其他情况准备就绪时,将执行默认情况。上面的程序用下面的默认情况重写。

package main

import "fmt"

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

Run in playground

上面的程序将打印,

default case executed  

同样,即使Select只有nil通道,也会执行默认情况。

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

    }
}

Run in playground

在上面的程序中,我们正在尝试从第 18行的 select 中ch读取内容。 如果default不存在该情况,则会select永远阻塞并导致死锁。由于我们在 select 中有一个 default case,因此它将被执行并且程序将打印,

default case executed  

随机Select

当一条语句中的多个case都select准备好时,会随机执行其中一个。

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

Run in playground

在上面的程序中, 分别调用了server1server2。然后主程序在第20行休眠 1 秒。当程序到达select第 21行的语句时。,server1将已写入from server1通道output1server2已写入from server2通道output2,因此 select 语句的两种情况都已准备好执行。如果多次运行该程序,输出将在随机Select的情况之间变化from server1from server2取决于随机Select的情况。

请在本地系统中运行此程序以获得此随机性。如果这个程序在Run in playground运行,它将打印相同的输出,因为Run in playground是确定性的。

空Select

package main

func main() {  
    select {}
}

Run in playground

你认为上面的程序的输出会是什么?

我们知道 select 语句会阻塞,直到它的其中一个 case 被执行。在这种情况下,select 语句没有任何情况,因此它将永远阻塞,从而导致死锁。该程序将出现以下输出,

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:  
main.main()  
    /tmp/sandbox246983342/prog.go:4 +0x25

本教程到此结束。祝你有美好的一天。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值