5-Go的select监听

一、select简介

  • select作用:Go里面提供了一个关键字select,通过select可以监听channel上的数据流动
  • select用法:select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述
  • select限制:与switch语句相比, select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作
  • select语法
    select {
    case <-chan1:
        // 如果chan1成功读到数据,则进行该case处理语句
    case chan2 <- 1:
        // 如果成功向chan2写入数据,则进行该case处理语句
    default:
        // 如果上面都没有成功,则进入default处理流程
    }

二、select的运行原理

  • select原理:在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句
    • 如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用
    • 如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况
      • 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复
      • 如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去
  • select中的default建议:建议不要在select中使用default
    • 如果没有case可以执行,那么会执行default,这称为"忙轮询"
    • 不使用default的话,没有case可以执行会阻塞,此时会释放对应的资源,出让CPU,系统效率更高
package main

import (
	"fmt"
	"runtime"
	"time"
)

func main() {
	ch := make(chan int)    // 用来进行数据通信的 channel
	quit := make(chan bool) // 用来判断是否退出的 channel
	//ch2 := make(chan string)
	go func() { // 写数据
		for i := 0; i < 5; i++ {
			ch <- i
			time.Sleep(time.Second)
		}
		close(ch)
		quit <- true // 通知主go程 退出
		runtime.Goexit()
	}()
	for { // 主go程 select 监听 chan数据流动
		select {
		case num := <-ch: // 不可读,阻塞。可以读,将数据保存至num
			fmt.Println("读到:", num) // 模拟使用数据

		case <-quit: // 不可读,阻塞。可以读,将主go程结束。
			//break // break 跳出 select	不可用
			//runtime.Goexit()	// 终止 主 go 程		不可用
			return // 终止进程
		}
		fmt.Println("============") // select 自身不带有循环机制,需借助外层 for 来循环监听
	}
}

三、select的注意事项

  • ①.监听的case中,没有满足监听条件,阻塞
  • ②.监听的case中,有多个满足监听条件,任选一个执行
  • ③.可以使用default来处理所有case都不满足监听条件的状况。 通常不用(会产生忙轮询)
  • ④.select 自身不带有循环机制,需借助外层 for 来循环监听
  • ⑤.break 跳出 select中的一个case选项 。类似于switch中的用法

四、select案例:斐波那契数列

package main

import (
	"fmt"
	"runtime"
)

func fibonacci(ch <-chan int, quit <-chan bool) {
	for {
		select {
		case num := <-ch:
			fmt.Print(num, " ")
		case <-quit:
			//return
			runtime.Goexit() //等效于 return
		}
	}
}

func main() {
	ch := make(chan int)
	quit := make(chan bool)

	go fibonacci(ch, quit) // 子go 程 打印fibonacci数列

	x, y := 1, 1
	for i := 0; i < 40; i++ {
		ch <- x
		x, y = y, x+y
	}
	quit <- true
}

五、select超时

  • 超时的需求引出:有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时
  • select的超时实现:select监听 time.After() 中 channel 的读事件;如果定时时间到,系统会向该channel中写入系统当前时间
package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int)
	quit := make(chan bool)
	go func() { // 子go 程获取数据
		for {
			select {
			case num := <-ch:
				fmt.Println("num = ", num)
			case <-time.After(3 * time.Second):	// 每次循环都会重新计时
				quit <- true
				goto label
				// return
				// runtime.Goexit()
			}
		}
	label:
		fmt.Println("break to label")
	}()
	for i := 0; i < 2; i++ {
		ch <- i
		time.Sleep(time.Second * 2)
	}
	<-quit // 主go程,阻塞等待 子go程通知,退出
	fmt.Println("finish!!!")
}

/* 调试输出
num =  0
num =  1
finish!!!
break to label
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值