自留地

Go Select 详解

2020-04-25  本文已影响0人  MR_Zzzzzz_

导读

select是一种go可以处理多个通道之间的机制,看起来和switch语句很相似,但是select其实和IO机制中的select一样,多路复用通道,随机选取一个进行执行,如果说通道(channel)实现了多个goroutine之前的同步或者通信,那么select则实现了多个通道(channel)的同步或者通信,并且select具有阻塞的特性。

示例

package main
import (
    "fmt" 
    "time"
)

func main()  {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func1 ()  {
        time.Sleep(time.Second)
        ch1 <- 1
    }()

    go func2 ()  {
        ch2 <- 3
    }()

    select {
    case i := <-ch1:
        fmt.Printf("从ch1读取了数据%d", i)
    case j := <-ch2:
        fmt.Printf("从ch2读取了数据%d", j)
    }
}

上面这段代码很简单,我们创建了两个无缓冲的channel,通过两个goroutine向ch1ch2两个通道发送数据,通过select随机读取ch1ch2的返回值,但是由于func1有sleep,所以这个例子我们总是从ch2读到结果,打印从ch2读取了数据3

场景

select这个特性到底有什么用呢,下面我们来介绍一些使用select的场景

  1. 竞争选举
    select {
    case i := <-ch1:
        fmt.Printf("从ch1读取了数据%d", i)
    case j := <-ch2:
        fmt.Printf("从ch2读取了数据%d", j)
    case m := <- ch3
        fmt.Printf("从ch3读取了数据%d", m)
    ...
    }

这个是最常见的使用场景,多个通道,有一个满足条件可以读取,就可以“竞选成功”

  1. 超时处理(保证不阻塞)
select {
    case str := <- ch1
        fmt.Println("receive str", str)
    case <- time.After(time.Second * 5): 
        fmt.Println("timeout!!")
}

因为select是阻塞的,我们有时候就需要搭配超时处理来处理这种情况,超过某一个时间就要进行处理,保证程序不阻塞。

  1. 判断buffered channel是否阻塞
package main
import (
    "fmt" 
    "time"
)

func main()  {
    bufChan := make(chan int, 5)
    
    go func ()  {
        time.Sleep(time.Second)
        for {
            <-bufChan
            time.Sleep(5*time.Second)
        }
    }() 
    

    for {
        select {    
        case bufChan <- 1:  
            fmt.Println("add success")
            time.Sleep(time.Second)  
        default:        
            fmt.Println("资源已满,请稍后再试")
            time.Sleep(time.Second) 
        } 
    }
}

这个例子很经典,比如我们有一个有限的资源(这里用buffer channel实现),我们每一秒向bufChan传送数据,由于生产者的生产速度大于消费者的消费速度,故会触发default语句,这个就很像我们web端来显示并发过高的提示了,小伙伴们可以尝试删除go func中的time.Sleep(5*time.Second),看看是否还会触发default语句

  1. 阻塞main函数
    有时候我们会让main函数阻塞不退出,如http服务,我们会使用空的select{}来阻塞main goroutine
package main
import (
    "fmt"
    "time"
)

func main()  {
    bufChan := make(chan int)
    
    go func() {
        for{
            bufChan <-1
            time.Sleep(time.Second)
        }
    }()


    go func() {
        for{
            fmt.Println(<-bufChan)
        }
    }()
     
    select{}
}

如上所示,这样主函数就永远阻塞住了,这里要注意上面一定要有一直活动的goroutine,否则会报deadlock。大家还可以把select{}换成for{}试一下,打开系统管理器看下CPU的占用变化。

上一篇下一篇

猜你喜欢

热点阅读