一文掌握 Go 语言 Select 的四大用法

栏目: IT技术 · 发布时间: 6年前

内容简介:本文作者:appleboy原文链接:https://blog.wu-boy.com/2019/11/four-tips-with-select-in-golang/

点击上方蓝色“ Go语言中文网 ”关注我们, 领全套 Go 资料 ,每天学习 Go 语言

本文作者:appleboy

原文链接:https://blog.wu-boy.com/2019/11/four-tips-with-select-in-golang/

一文掌握 Go 语言 Select 的四大用法

本文带大家认识 Go 语言的 Select 用法,相信大家对于 switch 并不陌生,然而 select 跟 switch 有个共同特性就是都通过 case 的方式来处理,但是 select 跟 switch 处理的事情完全不同,也完全不相容。来看看 switch 有什么特性: 各种类型及型别操作,接口 interface{} 型别判断 variable.(type),重点是会依照 case 顺序依序执行。先看个例子:

package main

var (
    i interface{}
)

func convert(i interface{}) {
    switch t := i.(type) {
    case int:
        println("i is interger", t)
    case string:
        println("i is string", t)
    case float64:
        println("i is float64", t)
    default:
        println("type not found")
    }
}

func main() {
    i = 100
    convert(i)
    i = float64(45.55)
    convert(i)
    i = "foo"
    convert(i)
    convert(float32(10.0))
}

运行出来的结果如下:

i is interger 100
i is float64 +4.555000e+001
i is string foo
type not found

而 select 的特性就不同了,只能接收 channel,否则会出错,而 default 会直接执行,所以没有 default 的 select 就会遇到 blocking,假设没有送 value 进去 Channel 就会造成 panic,底下拿几个实际例子来解说。

Random Select

同一个 channel 在 select 会随机选取,底下看个例子:

package main

import "fmt"

func main() {
    ch := make(chan int, 1)

    ch <- 1
    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

执行后会发现有时候拿到 random 01 有时候拿到 random 02,这就是 select 的特性之一,case 是随机选取,所以当 select 有两个 channel 以上时,如果同时对全部 channel 送资料,则会随机选取到不同的 Channel。而上面有提到另一个特性『假设没有送 value 进入 Channel 就会造成 panic』,拿上面例子来改:

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    }
}

执行后会发现变成 deadlock,造成 main 主程式爆炸,这时候可以直接用 default 方式解决此问题:

func main() {
    ch := make(chan int, 1)

    select {
    case <-ch:
        fmt.Println("random 01")
    case <-ch:
        fmt.Println("random 02")
    default:
        fmt.Println("exit")
    }
}

主程式 main 就不会因为读不到 channel value 造成整个程式 deadlock。

Timeout 超时机制

用 select 读取 channle 时,一定会实作超过一定时间后就做其他事情,而不是一直 blocking 在 select 内。底下是简单的例子:

package main

import (
    "fmt"
    "time"
)

func main() {
    timeout := make(chan bool, 1)
    go func() {
        time.Sleep(2 * time.Second)
        timeout <- true
    }()
    ch := make(chan int)
    select {
    case <-ch:
    case <-timeout:
        fmt.Println("timeout 01")
    }
}

建立 timeout channel,让其他地方可以透过 trigger timeout channel 达到让 select 执行结束,也或者有另一个写法是透握 time.After 机制

select {
case <-ch:
case <-timeout:
    fmt.Println("timeout 01")
case <-time.After(time.Second * 1):
    fmt.Println("timeout 02")
}

可以注意 time.After 是回传 chan time.Time,所以执行 select 超过一秒时,就会输出 timeout 02。

检查 channel 是否已满

直接来看例子比较快:

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int, 1)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

先宣告 buffer size 为 1 的 channel,先丢值把 channel 填满。这时候可以透过 select + default 方式来确保 channel 是否已满,上面例子会输出 channel blocking,我们再把程式改成底下

func main() {
    ch := make(chan int, 2)
    ch <- 1
    select {
    case ch <- 2:
        fmt.Println("channel value is", <-ch)
        fmt.Println("channel value is", <-ch)
    default:
        fmt.Println("channel blocking")
    }
}

把 buffer size 改为 2 后,就可以继续在塞 value 进去 channel 了,这边的 buffer channel 观念可以看之前的文章 『用五分钟了解什么是 unbuffered vs buffered channel』 [1]

select for loop 用法

如果你有多个 channel 需要读取,而读取是不间断的,就必须使用 for + select 机制来实现,更详细的实作可以参考『15 分钟学习 Go 语言如何处理多个 Channel 通道』

package main

import (
    "fmt"
    "time"
)

func main() {
    i := 0
    ch := make(chan string, 0)
    defer func() {
        close(ch)
    }()

    go func() {
    LOOP:
        for {
            time.Sleep(1 * time.Second)
            fmt.Println(time.Now().Unix())
            i++

            select {
            case m := <-ch:
                println(m)
                break LOOP
            default:
            }
        }
    }()

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

上面例子可以发现执行后如下:

其实把 default 拿掉也可以达到目的

select {
case m := <-ch:
    println(m)
    break LOOP

当没有值送进来时,就会一直停在 select 区段,所以其实没有 default 也是可以正常运作的,而要结束 for 或 select 都需要透过 break 来结束,但是要在 select 区间直接结束掉 for 回圈,只能使用 break variable 来结束,这边是大家需要注意的地方。

参考资料

[1]

『用五分钟了解什么是 unbuffered vs buffered channel』: https://blog.wu-boy.com/2019/04/understand-unbuffered-vs-buffered-channel-in-five-minutes/

推荐阅读

喜欢本文的朋友,欢迎关注“ Go语言中文网

一文掌握 Go 语言 Select 的四大用法

Go语言中文网启用微信学习交流群,欢迎加微信: 274768166 ,投稿亦欢迎


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to Genetic Algorithms

An Introduction to Genetic Algorithms

Melanie Mitchell / MIT Press / 1998-2-6 / USD 45.00

Genetic algorithms have been used in science and engineering as adaptive algorithms for solving practical problems and as computational models of natural evolutionary systems. This brief, accessible i......一起来看看 《An Introduction to Genetic Algorithms》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换