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

golang 通道和select超时

ztj100 2025-01-01 23:52 54 浏览 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通信均无法实施时,执行该默认分支
}

相关推荐

sharding-jdbc实现`分库分表`与`读写分离`

一、前言本文将基于以下环境整合...

三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么

在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...

MySQL8行级锁_mysql如何加行级锁

MySQL8行级锁版本:8.0.34基本概念...

mysql使用小技巧_mysql使用入门

1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...

MySQL/MariaDB中如何支持全部的Unicode?

永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...

聊聊 MySQL Server 可执行注释,你懂了吗?

前言MySQLServer当前支持如下3种注释风格:...

MySQL系列-源码编译安装(v5.7.34)

一、系统环境要求...

MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了

对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...

MySQL字符问题_mysql中字符串的位置

中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...

深圳尚学堂:mysql基本sql语句大全(三)

数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...

MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?

大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...

一文讲清怎么利用Python Django实现Excel数据表的导入导出功能

摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...

用DataX实现两个MySQL实例间的数据同步

DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...

MySQL数据库知识_mysql数据库基础知识

MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...

如何为MySQL中的JSON字段设置索引

背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...

取消回复欢迎 发表评论: