百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

golang 通道和select超时

ztj100 2025-01-01 23:52 41 浏览 0 评论

在某些场景下我们需要同时从多个通道接收数据。通道在接收数据时, 如果没有数据可以接收将会发生阻塞。你也许会写出如下代码使用遍历的方式来实现:

for{
    // 尝试从ch1接收值
    data, ok := <-ch1
    // 尝试从ch2接收值
    data, ok := <-ch2
    ......
}

这种方式虽然可以实现从多个通道接收值的需求, 但是运行性能会差很多。为了应对这种场景, Go内置了select关键字, 可以同时响应多个通道的操作。

select作用

Go里面提供了一个关键字select, 通过select可以监听channel上的数据流动。

select的用法与switch语言非常类似, 由select开始一个新的选择块, 每个选择条件由case语句来描述。

与switch的用法与switch语言非常相似, 由select开始一个新的选择块, 每个选择条件由case语句来描述。

与switch语句可以选择任何可使用相等比较的条件相比, select有比较多的限制, 其中最大的一条限制就是每个case语句里必须是一个IO操作, 大致的结构如下:

select {
    case <-chan1:
    //如果chan1成功读到数据, 则进行该case处理语句
    case chan2 <- -1
    //如果成功向chan2写入数据, 则进行该case处理语句
    default:
    //如果上面都没有成功, 则进入default处理流程
}

在一个select语句中, GO语言会按顺序从头到尾评估每一个发送和接收的语句。

如果没有任意一条语句可以执行(即所有的通道都被阻塞), 那么有两种可能的情况:

如果给出了default语句, 那么就会执行default语句, 同时程序的执行会从select语句后的语句中恢复

如果没有default语句, 那么select语句将被阻塞, 直到至少有一个通信可以进行下去。

package main
import (
"fmt"
)
func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for { // 为何 这里需要for 因为下面的写入语句是 for 循环写入的
        //监测channel数据流动
        select {
            case c <- x: //写入数据
            x, y = y, x+y
            case <-quit: //读取数据
            fmt.Println("quit")
            return
        }
    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 6; i++ {
            fmt.Println(<-c) //读取数据, 并打印数据
        }
    quit <- 0 //写入数据, 避免堵塞, 如果注释掉, err, fatal error: all goroutines are asleep - deadlock!
    }()
    fibonacci(c, quit)
}

超时

有时候会出现goroutine阻塞的情况, 那么我们如何避免整个程序进入阻塞的情况呢? 我们可以利用select来设置超时, 通过如下的方式实现。

package main
import (
"fmt"
"time"
)
func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
            case v := <-c: //读取数据
                fmt.Println(v)
            case <-time.After(3 * time.Second): //延时3秒
                fmt.Println("timeout")
                o <- true
                break
            }
        }
    }()
    for i := 0; i < 5; i++ {
        c <- i
        time.Sleep(time.Second)
    }
    <-o
    fmt.Println("程序结束")
}

运行结果:

0

1

2

3

4

timeout

程序结束


注意:

关键语法: <-time.After(3 * time.Second)

select可以同时监听一个或多个channel, 直到其中一个channel ready

package main
import (
"fmt"
"time"
)
func test1(ch chan string) {
    time.Sleep(time.Second * 5)
    ch <- "test1"
}
func test2(ch chan string) {
    time.Sleep(time.Second * 2)
    ch <- "test2"
}
func main() {
    // 2个管道
    output1 := make(chan string)
    output2 := make(chan string)
    // 跑2个子协程,写数据
    go test1(output1)
    go test2(output2)
    // 用select监控
    select {
    case s1 := <-output1:
    fmt.Println("s1=", s1)
    case s2 := <-output2:
    fmt.Println("s2=", s2)
    }
}

输出结果:

s2= test2

如果多个channel同时ready, 则随机选择一个执行

package main
import (
"fmt"
)
func main() {
    // 创建2个管道
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    go func() {
    //time.Sleep(2 * time.Second)
    int_chan <- 1
    }()
    go func() {
    string_chan <- "hello"
    }()
    select {
        case value := <-int_chan:
        fmt.Println("int:", value)
        case value := <-string_chan:
        fmt.Println("string:", value)
    }
    fmt.Println("main结束")
}

输出结果:

string: hello

main结束

可以用于判断管道是否存满

package main
import (
"fmt"
"time"
)
// 判断管道有没有存满
func main() {
    // 创建管道
    output1 := make(chan string, 10)
    // 子协程写数据
    go write(output1)
    // 取数据
    for s := range output1 {
    fmt.Println("res:", s)
    time.Sleep(time.Second)
    }
}
func write(ch chan string) {
    for {
    select {
        // 写数据
        case ch <- "hello":
        fmt.Println("write hello")
        default: //判断通道是否存满 适用于有缓冲通道
        fmt.Println("channel full")
        }
        time.Sleep(time.Millisecond * 500)
    }
}

输出结果:

write hello

channel full

res: hello

......

【实例】

package main
import (
"fmt"
"math/rand"
"time"
)
func generator() chan int {
    out := make(chan int)
    i := 0
    go func() {
    for {
    //func (r *Rand) Intn(n int) int 返回一个取值范围在[0,n)的伪随机int值,如果n<=0会panic。
    time.Sleep(time.Duration(rand.Intn(1500)) * time.Microsecond)
    out <- i
    i++
    }
    }()
    return out
}
//作为消费者
func worker(id int, c chan int) {
    for n := range c {
    time.Sleep(time.Second)
    fmt.Printf("Worker %d received %d\n", id, n)
    }
}
func createWorker(id int) chan<- int {
    c := make(chan int)
    go worker(id, c)
    return c
}
func main() {
    //var c1, c2 chan int // c1 and c2 = nil
    var c1, c2 = generator(), generator()
    var worker = createWorker(0)
    var values []int
    tm := time.After(10 * time.Second)
    tick := time.Tick(time.Second)
    for {
    var activeWorker chan<- int
    var activeValue int
    if len(values) > 0 {
    activeWorker = worker
    activeValue = values[0]
    }
    select {
    case n := <-c1:
    values = append(values, n)
    case n := <-c2:
    values = append(values, n)
    case activeWorker <- activeValue:
    values = values[1:]
    case <-time.After(800 * time.Millisecond): //单位: 毫秒
    fmt.Println("timeout")
    case <-tick:
    fmt.Println("queue len = ", len(values))
    case <-tm:
    fmt.Println("bye")
    return
    }
    }
}

channel 和 select 语法汇总:

c := make(chan int) // 创建一个无缓冲(unbuffered)的int类型的channel
c := make(chan int, 5) // 创建一个带缓冲的int类型的Channel
c <- x // 向channel c中发送一个值
<- c // 从channel c中接收一个值
x = <- c // 从channel c接收一个值并将其存储到变量x中
x, ok = <- c // 从channel c接收一个值。如果channel关闭了,那么ok将被置为false
for i := range c { ... ... } // for range与channel结合使用
close(c) // 关闭channel c
c := make(chan chan int) // 创建一个无缓冲的chan int类型的channel
func stream(ctx context.Context, out chan<- Value) error // 将只发送(send-only) channel作为函数参数
func spawn(...) <-chan T // 将只接收(receive-only)类型channel作为返回值
package main
import "fmt"
func main() {
c := make(chan chan int) // 创建一个无缓冲的chan int类型的channel 这里的语法没有错误
fmt.Println(c) ////0xc000048060
}

当涉及同时对多个 channel 进行操作时, 我们会结合使用到 Go 为 CSP 并发模型提供的另外一个原语: select。通过 select, 我们可以同时在多个 channel 上进行发送/接收操作:

select {
case x := <-c1: // 从channel c1接收数据
... ...
case y, ok := <-c2: // 从channel c2接收数据,并根据ok值判断c2是否已经关闭
... ...
case c3 <- z: // 将z值发送到channel c3中:
... ...
default: // 当上面case中的channel通信均无法实施时,执行该默认分支
}

相关推荐

SpringBoot整合SpringSecurity+JWT

作者|Sans_https://juejin.im/post/5da82f066fb9a04e2a73daec一.说明SpringSecurity是一个用于Java企业级应用程序的安全框架,主要包含...

「计算机毕设」一个精美的JAVA博客系统源码分享

前言大家好,我是程序员it分享师,今天给大家带来一个精美的博客系统源码!可以自己买一个便宜的云服务器,当自己的博客网站,记录一下自己学习的心得。开发技术博客系统源码基于SpringBoot,shiro...

springboot教务管理系统+微信小程序云开发附带源码

今天给大家分享的程序是基于springboot的管理,前端是小程序,系统非常的nice,不管是学习还是毕设都非常的靠谱。本系统主要分为pc端后台管理和微信小程序端,pc端有三个角色:管理员、学生、教师...

SpringBoot+LayUI后台管理系统开发脚手架

源码获取方式:关注,转发之后私信回复【源码】即可免费获取到!项目简介本项目本着避免重复造轮子的原则,建立一套快速开发JavaWEB项目(springboot-mini),能满足大部分后台管理系统基础开...

Spring Boot的Security安全控制——认识SpringSecurity!

SpringBoot的Security安全控制在Web项目开发中,安全控制是非常重要的,不同的人配置不同的权限,这样的系统才安全。最常见的权限框架有Shiro和SpringSecurity。Shi...

前同事2024年接私活已入百万,都是用这几个开源的SpringBoot项目

前言不得不佩服SpringBoot的生态如此强大,今天给大家推荐几款优秀的后台管理系统,小伙伴们再也不用从头到尾撸一个项目了。SmartAdmin...

值得学习的15 个优秀开源的 Spring Boot 学习项目

SpringBoot算是目前Java领域最火的技术栈了,除了书呢?当然就是开源项目了,今天整理15个开源领域非常不错的SpringBoot项目供大家学习,参考。高富帅的路上只能帮你到这里了,...

开发企业官网就用这个基于SpringBoot的CMS系统,真香

前言推荐这个项目是因为使用手册部署手册非常...

2021年超详细的java学习路线总结—纯干货分享

本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础...

jeecg-boot学习总结及使用心得(jeecgboot简单吗)

jeecg-boot学习总结及使用心得1.jeecg-boot是一个真正前后端分离的模版项目,便于二次开发,使用的都是较流行的新技术,后端技术主要有spring-boot2.x、shiro、Myb...

后勤集团原料管理系统springboot+Layui+MybatisPlus+Shiro源代码

本项目为前几天收费帮学妹做的一个项目,JavaEEJSP项目,在工作环境中基本使用不到,但是很多学校把这个当作编程入门的项目来做,故分享出本项目供初学者参考。一、项目描述后勤集团原料管理系统spr...

白卷开源SpringBoot+Vue的前后端分离入门项目

简介白卷是一个简单的前后端分离项目,主要采用Vue.js+SpringBoot技术栈开发。除了用作入门练习,作者还希望该项目可以作为一些常见Web项目的脚手架,帮助大家简化搭建网站的流程。...

Spring Security 自动踢掉前一个登录用户,一个配置搞定

登录成功后,自动踢掉前一个登录用户,松哥第一次见到这个功能,就是在扣扣里边见到的,当时觉得挺好玩的。自己做开发后,也遇到过一模一样的需求,正好最近的SpringSecurity系列正在连载,就结...

收藏起来!这款开源在线考试系统,我爱了

大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!今天小编推荐一款基于Spr...

Shiro框架:认证和授权原理(shiro权限认证流程)

优质文章,及时送达前言Shiro作为解决权限问题的常用框架,常用于解决认证、授权、加密、会话管理等场景。本文将对Shiro的认证和授权原理进行介绍:Shiro可以做什么?、Shiro是由什么组成的?举...

取消回复欢迎 发表评论: