• golang 信道的讲解与应用--channel(五)


    其实关于 golang 信道网上资料很多很全面了,个人感觉也没什么需要特别注意的坑。但是为了 golang 系列的的完整性,我还是开了这一篇博客,为大家提供一些 golang 使用的例子,多一些参考文档。

    在上一篇中我们提到 golang 通过 go 程提供了非常优秀的高并发能力,那么 go 程之间的通信就是通过 channel 来进行的。和 go 程一样,golang 在语法上就支持 channel,golang 为 chennle 专门实现了一种变量类型 chan。可以和创建普通变量一样创建 channel。另外 chan 类型的变量的一个重要特性:线程安全。其他类型变量在高并发场景,如果要直接操作这个变量需要先加锁,chan 则完全避免了这个烦恼。

    我们来直接看例子,这个例子时基于上一章的第一个例子做了些许的改动:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        ch := make(chan int)
        go func(ch chan int) {
            fmt.Println("Hello, I am a goroutine.")
            ch <- 0
        }(ch)
        <-ch
        fmt.Println("Hello, I am the main goroutine.")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出:

    Hello, I am a goroutine.
    Hello, I am the main goroutine.
    
    • 1
    • 2

    这里我们就没有用到 time.Sleep,在上一章节我们提到如果没有 Sleep 主 go 程就会先退出,还没等子 go 程执行完毕整个程序就结束了。这里我们用来一种比 Sleep 更优雅的方式 chan 使主 go 程阻塞,等等子 go 程想 chan 写入数据,主 go 程收到数据才继续往下执行。这里借助了 channel 另一个比较重要的特性:当 channel 中没有数据,读时就会阻塞;当 channel 满了之后向 channel 写数据也就会阻塞。通过这种方式就能及时知道子 go 程退出了,避免了长时间的等待。

    另外需要特别注意的是阻塞发生的时机,下面例子需要实际运行看结果,大家可以自己运行一下对比结果:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch := make(chan int)
        go func(ch chan int) {
            for {
                time.Sleep(5 * time.Second)
                <-ch
                fmt.Println("子 go 程从 ch 中读到数据")
                // 把 time.Sleep(5 * time.Second) 挪到这儿试试
            }
        }(ch)
        fmt.Println("准备向 channel 写入数据")
        ch <- 0
        fmt.Println("写入成功")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    另外在有些情况我们并不希望因为 channel 数据接收端(消费者)的 go 程处理数据过慢而阻塞 channel 数据发送端(生产者)的 go 程,这时候我们可以使用带缓存的 channel。可以使用下面方法创建带缓存的 channel。

    ch := make(chan int, 10)
    
    • 1

    这将会创建一个 10 个缓存空间的 channel。

    单方向的 channel

    channel 还可以设置成只读或者只写

    创建只写 channel:

    var ch chan<- int
    
    • 1

    或者:

    ch := make(chan<- int, 10)
    
    • 1

    创建只读 channel

    var ch <-chan int
    
    • 1

    或者:

    ch := make(<-chan int, 10)
    
    • 1

    特别注意:如果直接使用单方向的 channel 那么程序必然或阻塞。正确的使用方式是先创建一个正常的 channel,然后在隐式的转为只读和只写,生产者使用只写 channel,消费者使用只读 channel,如下:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch := make(chan int, 10)
        go producer(ch)
        go consumer(ch)
        time.Sleep(1 * time.Millisecond)
    }
    
    func producer(ch chan<- int) {
        a := 10
        fmt.Printf("生产者发送数据:%d\n", a)
        ch <- a
    }
    
    func consumer(ch <-chan int) {
        a := <-ch
        fmt.Printf("消费者接收到数据:%d\n", a)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    输出:

    生产者发送数据:10
    消费者接收到数据:10
    
    • 1
    • 2

    在前面第二章节讲过 channel 类型的数据变量事一个引用,这里通过函数传参的方式进行隐式的转换只是作用在了引用本身上,实际转换前和转换后变量对应的底层数据是一样的。

  • 相关阅读:
    linux用户信息
    【斗破年番】火火小医仙幽会,彩鳞吃醋跟随,失身之事终暴露,蛇人族来算账
    快速排序压缩算法2024年最新一种压缩算法
    左叶子之和-力扣404-C++
    使用 PowerShell和工具软件在 Windows 中更改文件时间戳
    Html学习
    快手推出快手虚拟演播助手
    如何在 Linux 上部署 RabbitMQ
    在项目中应用SpringSecurity权限控制
    fastadmin框架如何开启事务
  • 原文地址:https://blog.csdn.net/yjf147369/article/details/126512801