Golang中这些channel用法你了解吗

 更新时间:2023年08月30日 09:44:21   作者:阿兵云原生  
channel 是GO语言中一种特殊的类型,是连接并发goroutine的管道,这篇文章主要来和大家分享一下关于 nil channel 通道,有缓冲通道,无缓冲通道的常用方法以及巧妙使用的方式,希望对大家有所帮助

channel 是什么

channel 是GO语言中一种特殊的类型,是连接并发goroutine的管道

channel 通道是可以让一个 goroutine 协程发送特定值到另一个 goroutine 协程的通信机制

关于 channel 的原理,channel通道需要注意的地方,之前有分享过,可以查看如下文章

本次,我们主要分享的是关于 nil channel 通道,有缓冲通道,无缓冲通道 的常用方法以及巧妙使用的方式

巧用 nil 的 channel 通道

平日里使用的 channel 通道都是使用无缓冲,或者有缓冲的 channel 通道,或许使用为 nil 的 channel 通道还是比较少,甚至都不知道如何去使用 nil 的 channel 通道

我们先来看这么一个例子

  • 创建两个 channel c1, c2 ,数据类型是 struct{} ,用于占位
  • 分别开辟两个子协程,其中子协程 1 在 2 秒之后写入数据给到 c1,另外一个子协程 2 在 1 秒之后写入数据给到 c2
  • 主协程循环等待阻塞读取 c1 , c2 里面的数据,读取后将对应的标识 ok1 / ok2 置为 true
  • 当 ok1 和 ok2 都为 true 的时候,退出循环,结束程序
func main() {
    c1, c2 := make(chan struct{}), make(chan struct{})
    go func() {
        time.Sleep(time.Second * 2)
        c1 <- struct{}{}
        //close(c1)
    }()
    go func() {
        time.Sleep(time.Second * 1)
        c2 <- struct{}{}
       // close(c2)
    }()
    var (
        ok1 bool
        ok2 bool
    )
    for {
        select {
        case <-c1:
                ok1 = true
                fmt.Println("1")
        case <-c2:
                ok2 = true
                fmt.Println("2")
        }
        if ok1 && ok2 {
                break
        }
    }
    fmt.Println("program termination ... ")
}

运行结果如下:

2
1
program termination ...

看上去效果一切正常,若此时,我们将上述代码中的 close(c1) 和 close(c2) 的注释去掉,我们再查看一下结果就回是这样的:

...
2
2
2
1
program termination ...

出现这样的问题是什么呢?是因为我们 close channel 通道之后,若还对这个通道写入数据会 panic,若还从这个通道读取数据会立即返回该通道类型的零值,而不会阻塞等待数据

因此才会有上述情况,那么这个时候,我就可以很好的用好这个 nil 的 channel,咱就可以这样来调整一下关于通道使用的情况

修改为,从通道中读取数据时,先判断通道是否已经关闭,若关闭则将通道设置为 nil,若未关闭,则打印我们从通道中读取的数据(此处模拟直接打印一个固定的值)

for {
    select {
    case _, ok := <-c1:
        if !ok {
            c1 = nil
        }else{
            fmt.Println("1")
        }
    case _, ok := <-c2:
        if !ok {
            c2 = nil
        }else{
            fmt.Println("2")
        }
    }
    if c1 == nil && c2 == nil {
            break
    }
}

这种时候,我们就知道对于从通道中读取数据,先去判断通道是否关闭,若通道关闭了,那么我们直接显示的给通道设置为 nil

这里是否会有这么一个疑问?关闭通道,通道变量不应该就变成 nil 了吗?为什么我们还要自己去设置为 nil?

实际上这就是我们对于通道的基础知识不扎实了,关闭通道后,通道本身并不会变为 nil。通道变量仍然持有通道的地址,只是通道的状态变为了已关闭

巧用无缓冲 channel 通道

对于无缓冲的 channel 通道,只有在对其进行接收操作的 goroutine 协程和对其进行发送操作的 goroutine 协程都存在的情况下,通信才能进行,否则单方面的操作会让对应的 goroutine 协程陷入阻塞状态,因为该 channel 通道没有缓冲

使用无缓冲的 channel 通道,我们可以用在如下几个方面

1.信号传递

信号传递我们就可以用在两个协程一对一的传递信号上面,当然我们也可以使用在主协程主动通知所有子协程关闭的全场景下,这就是一对多的传递信号,相关的 demo 可以在这期文章中有展示

GO 语言的并发模式

一对一(一个发一个收)

一对多(一个发多个收,此处可以是 协程 1 close 掉 通道,那么 多个协程默认都能够读取到通道的值是零值,此时多个子协程就可以根据通道的关闭状态来处理后续的逻辑)

2.控制同步

GO语言倡导我们不要通过共享内存来通信,而应该通过通信来共享内存,此处 channel 就是这样设计的,当然如果需要有更高的性能,那么我们还是可以使用更加低级的GO语言原语 sync 包中的锁机制

可以点击查看往期文章:sync 锁机制

巧用有缓冲 channel 通道

1.用作队列

用作队列应该是比较好理解的,队列先入先出 FIFO,给 channel 通道设置明确的缓冲区,例如 ch:=make(chan int, 10)

多个协程就可以异步的并发处理该队列,由于有缓冲的 channel 通道中有一定的容量,因此,对于协程读取通道中数据时,存在阻塞的情况相对无缓冲的通道来说就会少很多,相应的在一定程度上就提升了性能

对于有缓冲的 channel 通道,channel 通道满的时候,写入数据会阻塞,读取数据正常处理, channel 通道空的时候,写入数据正常,读取数据会阻塞

2.用作信号量

有缓冲的 channel 通道还可以用来计数,例如我们有 15 个 job,可是目前只有 3 个 worker,那么同一时间,只会有 3 个worker 来干活,我们就可以使用通道来查看目前有多少个 worker 在工作,写一个简单的 demo

  • 创建 j 和 worker channel 通道,
  • 子协程 1 写 15 个任务给到 j 通道中,写完 15 个任务到 j 中便关闭自己的通道(因为后续我们需要使用 for...range 的方式读取通道)
  • 使用 sync.WaitGroup 管控开辟的 3 个协程,模拟 3 个 工人去干活
  • 能够从写入数据到 worker channel 通道中,则开始干活,干完之后,从 worker channel 通道中读出数据
func main() {
    j := make(chan int, 15)
    worker := make(chan int, 3)
    go func() {
        for i := 0; i < 15; i++ {
            j <- i
        }
        close(j)
    }()
    var wg sync.WaitGroup
    for job := range j {
        wg.Add(1)
        go func(job int) {
            defer wg.Done()
            worker <- job
            // 模拟干活
            fmt.Println("正在执行 job : ", job)
            time.Sleep(time.Second * 1)
            <-worker
        }(job)
    }
    wg.Wait()
    fmt.Println("program termination ... ")
}

感兴趣的 xdm 的可以复制代码运行一下,可以看到效果是 3 个 job 一起打印,间隔 1 秒后,又是 3 个 job 一起打印的

select 和 channel 通道如何结合使用?

1.心跳

func main() {
    h := time.NewTicker(2 * time.Second)
    defer h.Stop()
    for {
        select {
        case <-h.C:
            // 模拟处理心跳
            fmt.Println("hhh")
        }
    }
}

2.使用 default

select ...default 这个组合就不必过多赘述了,就是在我们阻塞读取通道数据时,若当前时间没有从任何一个通道中读取到数据,则默认走 default 里面的逻辑

3.超时机制

超时机制使用的也是非常频繁的,很多时候为了方便,可能我们会使用例如<- time.After(10 * time.Second) 的方式,使用这种方式,GO 语言会维护一个最小堆,当时间到了,通道被唤醒的时候,就会从最小堆顶取出 timer 对象,再执行 timer 中的函数,执行完毕之后,自行就会做删除,自行就会做 GC

可是在上述这种方式使用比较多的时候,会给程序带来 GC 的压力,我们完全可以入如下方式来实现超时机制,显示的去做 GC

func main() {
    c := time.NewTimer(10 * time.Second)
    defer c.Stop()
    for {
        select {
        case <-c.C:
            fmt.Println("program overtime ")
            return
        }
    }
}

总结

本次演示了关于 nil channel,有缓冲 channel ,无缓冲 channel , select 如何与 channel 配合使用,上述 demo 完全可以复制下来,xdm 可以自行运行,查看效果

以上就是Golang中这些channel用法你了解吗的详细内容,更多关于Go channel的资料请关注脚本之家其它相关文章!

相关文章

  • GoFrame框架数据校验之校验对象校验结构体

    GoFrame框架数据校验之校验对象校验结构体

    这篇文章主要为大家介绍了GoFrame框架数据校验之校验对象校验结构体示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • golang中context.WithValue的使用规范问题小结

    golang中context.WithValue的使用规范问题小结

    本文主要介绍了golang中context.WithValue的使用规范问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • Go语言里切片slice的用法介绍

    Go语言里切片slice的用法介绍

    这篇文章介绍了Go语言里切片slice的用法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Golang Gin框架中间件的用法详解

    Golang Gin框架中间件的用法详解

    中间件是Gin框架中的一个核心概念,它允许开发者在处理HTTP请求的过程中插入自定义的钩子函数,从而实现诸如日志记录、身份验证、权限控制等公共逻辑,本文将结合实际案例,详细讲解Gin框架中间件的用法
    2024-10-10
  • 基于Go语言实现一个压测工具

    基于Go语言实现一个压测工具

    这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-01-01
  • 手把手教你如何在Goland中创建和运行项目

    手把手教你如何在Goland中创建和运行项目

    欢迎来到本指南!我们将手把手地教您在Goland中如何创建、配置并运行项目,通过简单的步骤,您将迅速上手这款强大的集成开发环境(IDE),轻松实现您的编程梦想,让我们一起开启这段精彩的旅程吧!
    2024-02-02
  • Golang数据类型比较详解

    Golang数据类型比较详解

    这篇文章主要围绕Golang数据类型比较详细展开,文中有详细的比较过程,需要的朋友可以参考一下
    2023-04-04
  • 使用Go语言构建高效的二叉搜索树联系簿

    使用Go语言构建高效的二叉搜索树联系簿

    树是一种重要的数据结构,而二叉搜索树(BST)则是树的一种常见形式,在本文中,我们将学习如何构建一个高效的二叉搜索树联系簿,感兴趣的可以了解下
    2024-01-01
  • GoLang string类型深入分析

    GoLang string类型深入分析

    string 作为 go 语言中的基础类型,其实有一些需要反复揣摩的,可能是我们使用的场景太简单,也可能是我们不需要那可怜的一点优化来提高性能,对它也就没那么上心了
    2023-01-01
  • Golang实现支付宝沙箱支付的方法步骤

    Golang实现支付宝沙箱支付的方法步骤

    本文主要介绍了Golang实现支付宝沙箱支付的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04

最新评论