姊妹篇:


Select vs Switch

 

case
switch..case
package main

import "fmt"

func convert(val interface{}) {

    switch t := val.(type) {
    case int:
        fmt.Println("val为int类型", t)
    case string:
        fmt.Println("val为string类型", t)
    case float64:
        fmt.Println("val为float64类型", t)
    case float32:
        fmt.Println("val为float32类型", t)
    case []string:
        fmt.Println("val为字符串类型的切片", t)
    default:
        fmt.Println("val不是上列类型之一")

    }

}

func main() {

    var i interface{}

    i = float32(3.1415926)
    convert(i)

    i = "dashen"
    convert(i)

    i = 100
    convert(i)

    i = []string{"欧拉", "高斯"}

    convert(i)

}

输出为:

val为float32类型 3.1415925
val为string类型 dashen
val为int类型 100
val为字符串类型的切片 [欧拉 高斯]

 


select..case

即每个 case 必须是一个通信操作, 要么是发送要么是接收

 

select 将随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。

  • 每个 case 都必须是一个通信

  • 所有 channel 表达式都会被求值

  • 如果有多个 case 都可以执行,Select 会随机公平地选出一个执行。其他不会执行。

    否则:

    如果有 default 子句,则执行该语句。

    如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。


 

Select的四大基本用法

 

随机选择

 

Random Select
package main

import "fmt"

func main() {
    var ch1, ch2, ch3 chan int
    var i1, i2 int

    select {
    case i1 = <-ch1:
        fmt.Println("接收到了管道1的一条数据:", i1)
    case ch2 <- i2:
        fmt.Println("向管道2发送了一条数据:", i2)

    case i3, ok := <-ch3:
        if ok {
            fmt.Println("收到了管道3的数据:", i3)
        } else {
            fmt.Println("管道3已被关闭")
        }

    default:
        fmt.Println("以上case皆不可运行,即没有进行通信")

    }

}

输出为:

以上case皆不可运行,即没有进行通信

如果把上述代码中的default语句块去掉,则会报

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
    /Users/shuangcui/go/note/select/2.go:9 +0x108
exit status 2

而如果将如上代码中的channel改成有缓存,如:

package main

import "fmt"

func main() {

    ch1 := make(chan int,1)
    ch2 := make(chan int,1)
    ch3 := make(chan int,1)

    ch6 := make(chan int,1)


    var i1, i2 int

    select {
    case i1 = <-ch1:
        fmt.Println("接收到了管道1的一条数据:", i1)
    case ch2 <- i2:
        fmt.Println("向管道2发送了一条数据:", i2)

    case i3, ok := <-ch3:
        if ok {
            fmt.Println("收到了管道3的数据:", i3)
        } else {
            fmt.Println("管道3已被关闭")
        }

    case ch6 <- 271828:
        fmt.Println("向管道6发送了一条数据:", 271828)

    default:
        fmt.Println("以上case皆不可运行,即没有进行通信")

    }

}

第二个case和第四个case是可以执行的,所以不会走default语句.输出为:

向管道2发送了一条数据: 0

向管道6发送了一条数据: 271828

二者都满足条件,被select选中执行的概率完全一样


 

超时控制

 

假设业务中需调用某接口A,要求超时时间为x秒,那么如何优雅、简洁的实现呢?

select+time.After
package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan string)

    go func() {
        time.Sleep(time.Second * 2)
        ch <- "写入某个值"

    }()

    select {
    case rs := <-ch:
        fmt.Println("结果是:", rs)

    case <-time.After(time.Second * 1):
        fmt.Println("超时!")
    }
}

输出为:

超时!

参考:

 

time.After
// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
    return NewTimer(d).C
}

接收一个int64类型的值,返回一个Time类型的单向channel


 

检查channel是否已满

 

package main

import "fmt"

func main() {

    ch := make(chan int, 1)

    ch <- 271828

    select {
    case ch <- 31415926:
        fmt.Println("通道的值为:", <-ch)
        fmt.Println("channel vaule is:",<-ch)

    default:
        fmt.Println("通道已经被阻塞,即已经满了")
    }

}

输出为:

通道已经被阻塞,即已经满了

将如上ch这个int类型通道的缓存值从1改称10,则

ch := make(chan int, 10)

则执行结果为:

通道的值为: 271828
channel vaule is: 31415926

 

在循环中使用select

 

for + select
package main

import (
    "fmt"
    "time"
)

func main() {

    i := 0

    ch := make(chan string, 0)

    defer func() {
        close(ch)
    }()

    go func() {
    CuiStartLoop: //不加也可以,与后面break后的 CuiStartLoop相呼应,作为循环体的标识
        for {
            time.Sleep(time.Second * 1)
            fmt.Println(time.Now().Unix())
            fmt.Println("当前i的值为:",i)
            i++

            select {

            case m := <-ch:
                fmt.Println("输出为:",m)
                break CuiStartLoop
            default:
                fmt.Println("执行了default代码块")
            }
        }
    }()

    time.Sleep(time.Second * 4)
    ch <- "stop"

}

输出:

1584289065
当前i的值为: 0
执行了default代码块
1584289066
当前i的值为: 1
执行了default代码块
1584289067
当前i的值为: 2
执行了default代码块
1584289068
当前i的值为: 3
输出为: stop

 

当没有值传送进来时, 就会一直停在 select 区段, 所以即便没有 default代码块 也是可以正常运作的.

而要结束 for 或 select 都需要通过 break 来结束, 但是要在 select 区间直接结束掉 for 循环, 只能使用 break.


参考:

 

更底层(操作系统层面)地进行理解: