go select 与 for 区别_28. 这五点带你理解 Go语言的 select 用法

本文介绍了Go语言中select的用法,与for的区别,包括最简单例子、防止死锁、select的随机性、超时处理以及读写操作。select主要用于channel操作,其case执行具有随机性,需要注意防止死锁并可设置超时。
摘要由CSDN通过智能技术生成

点击上方“Go编程时光”,选择“加为星标”

第一时间关注Go技术干货!

de32613f4666489d6f7382b9178e7765.png

系列导读

01. 开发环境的搭建(Goland & VS Code)

02. 学习五种变量创建的方法

03. 详解数据类型:整形与浮点型

04. 详解数据类型:byte、rune与string

05. 详解数据类型:数组与切片

06. 详解数据类型:字典与布尔类型

07. 详解数据类型:指针

08. 面向对象编程:结构体与继承

09. 一篇文章理解 Go 里的函数

10. Go语言流程控制:if-else 条件语句

11. Go语言流程控制:switch-case 选择语句

12. Go语言流程控制:for 循环语句

13. Go语言流程控制:goto 无条件跳转

14. Go语言流程控制:defer 延迟调用

15. 面向对象编程:接口与多态

16. 关键字:make 和 new 的区别?

17. 一篇文章理解 Go 里的语句块与作用域

18. 学习 Go 协程:goroutine

19. 学习 Go 协程:详解信道/通道

20. 几个信道死锁经典错误案例详解

21. 学习 Go 协程:WaitGroup

22. 学习 Go 协程:互斥锁和读写锁

23. Go 里的异常处理:panic 和 recover

24. 超详细解读 Go Modules 前世今生及入门使用

25. Go 语言中关于包导入必学的 8 个知识点

26. 如何开源自己写的模块给别人用?

27. 说说 Go 语言中的类型断言?

在线博客: golang.iswbm.com (可访问)
Github: github.com/iswbm/GolangCodingTime

前面写过两篇关于 switch-case 的文章,分别是:

11. Go语言流程控制:switch-case 选择语句

27. 说说 Go 语言中的类型断言?

今天要学习一个跟 switch-case 很像,但还有点个人特色select-case,这一节本应该放在《19. 学习 Go 协程:详解信道/通道》里一起讲的,但由于内容还不少,所以还是开一篇单独讲它。

跟 switch-case 相比,select-case 用法比较单一,它仅能用于 信道/通道 的相关操作。

select {
    case 表达式1:
        
    case 表达式2:
        
   default:
      
}

接下来,我们来看几个例子帮助理解这个 select 的模型。

1. 最简单的例子

先创建两个信道,并在 select 前往 c2 发送数据

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    c2 "hello"

    select {
    case msg1 :=       fmt.Println("c1 received: ", msg1)
    case msg2 :=       fmt.Println("c2 received: ", msg2)
    default:
      fmt.Println("No data received.")
    }
}

在运行 select 时,会运行所有(如果有机会的话)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,所以输出如下

c2 received:  hello

2. 避免造成死锁

select 在执行过程中,必须命中其中的某一分支。

如果在遍历完所有的 case 后,若没有命中(命中:也许这样描述不太准确,我本意是想说可以执行信道的操作语句)任何一个 case 表达式,就会进入 default 里的代码分支。

但如果你没有写 default 分支,select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误,就像下面这样子。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

    // c2 select {case msg1 :=         fmt.Println("c1 received: ", msg1)case msg2 :=         fmt.Println("c2 received: ", msg2)// default://  fmt.Println("No data received.")
    }
}

运行后输出如下

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
        /Users/MING/GolandProjects/golang-test/main.go:13 +0x10f
exit status 2

解决这个问题的方法有两种

一个是,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

  // c2 select {case msg1 :=         fmt.Println("c1 received: ", msg1)case msg2 :=         fmt.Println("c2 received: ", msg2)default:
    }
}

另一个是,让其中某一个信道可以接收到数据

package main

import (
    "fmt"
    "time"
)

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)

  // 开启一个协程,可以发送数据到信道
    go func() {
        time.Sleep(time.Second * 1)
        c2 "hello"
    }()

    select {
    case msg1 :=         fmt.Println("c1 received: ", msg1)
    case msg2 :=         fmt.Println("c2 received: ", msg2)
    }
}

3.  select 随机性

之前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。

通过下面这个例子的执行结果就可以看出

30cc81f50f7f3c5948f145e178777a99.png

4. select 的超时

当 case 里的信道始终没有接收到数据时,而且也没有 default 语句时,select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间。

package main

import (
    "fmt"
    "time"
)

func makeTimeout(ch chan bool, t int) {
    time.Sleep(time.Second * time.Duration(t))
    ch true
}

func main() {
    c1 := make(chan string, 1)
    c2 := make(chan string, 1)
    timeout := make(chan bool, 1)

    go makeTimeout(timeout, 2)

    select {
    case msg1 :=         fmt.Println("c1 received: ", msg1)
    case msg2 :=         fmt.Println("c2 received: ", msg2)
    case         fmt.Println("Timeout, exit.")
    }
}

输出如下

Timeout, exit.

5. 读取/写入都可以

上面例子里的 case,好像都只从信道中读取数据,但实际上,select 里的 case 表达式只要求你是对信道的操作即可,不管你是往信道写入数据,还是从信道读出数据。

package main

import (
    "fmt"
)

func main() {
    c1 := make(chan int, 2)

    c1 2
    select {
    case c1 4:
        fmt.Println("c1 received: ",         fmt.Println("c1 received: ",     default:
        fmt.Println("channel blocking")
    }
}

输出如下

c1 received:  2
c1 received:  4

6. 总结一下

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  1. select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;

  2. select 的 case 是随机的,而 switch 里的 case 是顺序执行;

  3. select 要注意避免出现死锁,同时也可以自行实现超时机制;

  4. select 里没有类似 switch 里的 fallthrough 的用法;

  5. select 不能像 switch 一样接函数或其他表达式。

留言纠错


由于这个号,没有留言功能,文章中不可避免(尽管我已经尽力验证充分)会出现一些小毛病。

为了避免文章因我个人语言表达不当,或者个人理解有误,而误导初学者,请大家共同监督,若文章有说的不对的地方,请在下方链接里留言,帮我指正。谢谢大家,共同努力。Fighting

“找茬”,”纠错“,请来这里留言!

a758c451025f5fa6fa9ce002a3825c56.png

喜欢本教程系列的同学欢迎长按下图订阅!

⬇⬇⬇

9c4363f492c6b1212b00f621337dbe5c.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值