Bruce Blog Bruce Blog
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航

Bruce Tao

运维界的该溜子
首页
  • CentOS
  • Ubuntu-Debian
  • 系统网络
  • 系统辅助工具
  • MySQL
  • Redis
  • Mongodb
  • Docker基础
  • Container基础
  • Kubernetes

    • Kubernetes基础
    • Kubernetes辅助
  • Container-Network
  • Jenkins
  • Gitlab
  • ArgoCD
  • Ansible
  • Terraform
  • AWS
  • MQ
  • NGINX
  • JumpServer
  • 基础
  • 函数模块
  • 框架
  • 基础

    • Golang环境
    • 语法
    • 数据类型与运算符
    • 分支语句
    • 循环语句
    • 数组
    • 切片
    • Map
    • String
    • 函数
    • 包的管理
    • 指针
    • 结构体
    • Go语言中的OOP
    • 方法和接口
    • 错误处理
  • Go进阶

    • Go进阶
  • Go框架

    • Go框架
  • Golang辅助

    • Golang辅助
  • CSS
  • HTML
  • JavaScript
  • 前端辅助
  • 常用命令
  • 性能监控工具
  • Windows下Docker使用
  • 日常学习
  • 其他导航
  • 基础

  • Go进阶

    • Go进阶
    • File操作
    • IO操作
    • 文件复制
    • 断点续传
    • bufio包
    • ioutil包
    • 遍历目录
    • 并发性Concurrency概念
    • Goroutine初识
    • Goroutine并发模型
    • Runtime包
    • 临界资源安全问题
    • sync包WaitGroup
    • 互斥锁
    • 读写锁
    • Channel通道
      • 关闭通道和通道上范围循环
      • 缓冲通道
      • 定向通道
      • time包中的通道相关函数
      • select语句
      • CSP并发模型
      • Go语言反射(一)
      • Go语言反射(二)
    • Go框架

    • Golang辅助

    • Golang
    • Go进阶
    Bruce
    2022-12-03
    目录

    Channel通道

    # 一、Channel通道

    通道可以被认为是Goroutine通信的管道.类似于管道中的水从一端到另一端的流动,数据可以从一端发送到另一端,通过通道接收.

    当多个Goroutine想实现共享数据的时候,虽然也提供了传统的同步机制,但是Go语言强烈建议的是使用Channel通道来实现Goroutines之间的通信.

    "不要通过共享内存来通信,而应该通过通信来共享内存"这是语句风靡golang社区的经典语

    Go语言中,要传递某个数据给另一个goroutine(协程),可以把一个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象.Go从语言层面保证同一个时间只有一个goroutine能够访问channel里面的数据,为开发者提供了一种优雅加单的工具,所以Go的做法就是使用channel来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine中传递,而不是使用共享内存来通信.

    # 二、什么是通道

    通道是goroutine之间的通道.它可以让goroutine之间相互通信.

    每个通道都有与其相关的类型.该类型是通道允许传输的数据类型.(通道的的零值为nil.nil通道没有任何用处,因此通道必须使用类似于map和切片的方法来定义.)

    # 通道的声明

    声明一个通道和定义一个变量的语法一样:

    //声明通道
    var 通道名 chan 数据类型
    //创建通道:如果通道为nil(就是不存在),就需要先创建通道
    通道名 = make(chan 数据类型)
    
    1
    2
    3
    4

    示例代码

    package main
    
    import "fmt"
    
    func main() {
        var a chan int
        if a == nil {
            fmt.Println("channel 是 nil 的, 不能使用,需要先创建通道。。")
            a = make(chan int)
            fmt.Printf("数据类型是: %T", a)
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    运行结果

    channel 是 nil 的,不能使用,需要先创建通道...
    数据类型: chan int
    
    1
    2

    也可以简短声明

    a := make(chan int)
    
    1
    # channel的数据类型

    channel是引用类型的数据,在作为参数传递的时候,传递的是内存地址.

    示例代码

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        ch1 := make(chan int)
        fmt.Printf("%T,%p\n",ch1,ch1)
    
        test1(ch1)
    
    }
    
    func test1(ch chan int){
        fmt.Printf("%T,%p\n",ch,ch)
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    运行结果

    image-20221124181236789

    能够看到,ch和ch1的地址是一样的,说明它们是同一个通道.

    # 通道的注意点

    Channel通道在使用的时候,有以下几个注意点:

    • 1.用于goroutine,传递消息的.
    • 2.通道,每个都有相关联的数据类型,nil chan,不能使用,类似于nil map,不能直接存储键值对
    • 3.使用通道传递数据: <- chan <- data,发送数据到通道.向通道中写数据data <- chan,从通道中获取数据.从通道中读数据
    • 4.阻塞: 发送数据: chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞 读取数据: data <- chan,也是阻塞的.直到另一条goroutine,写出数据解除阻塞
    • 5.本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作.

    最后: 通道是goroutine之间的连接,所以通道的发送和接收必须处在不同的goroutine中.

    # 三、通道的使用语法

    # 发送和接收

    发送和接收的语法

    data := <- a // read from channel a
    a <- data //write to channel a
    
    1
    2

    在通道上箭头的方向指定数据是发送还是接收.

    另外:

    v,ok := <- a //从一个channel中读取
    
    1
    # 发送和接收默认是阻塞的

    一个通道发送和接收数据,默认是阻塞的.当一个数据被发送到通道时,在发送语句中被阻塞,直到另一个Goroutine从该通道读取数据.相对地,当从通道读取数据时,读取被阻塞,直到一个Goroutine将数据写入该通道.

    这些通道的特性是帮助Goroutine有效地进行通信,而无需像使用其他编程语言中非常常见的显示锁或条件变量.

    示例代码

    package main
    
    import "fmt"
    
    func main() {
        var ch1 chan bool       //声明,没有创建
        fmt.Println(ch1)        //<nil>
        fmt.Printf("%T\n", ch1) //chan bool
        ch1 = make(chan bool)   //0xc0000a4000,是引用类型的数据
        fmt.Println(ch1)
    
        go func() {
            for i := 0; i < 10; i++ {
                fmt.Println("子goroutine中,i:", i)
            }
            // 循环结束后,向通道中写数据,表示要结束了。。
            ch1 <- true
    
            fmt.Println("结束。。")
    
        }()
    
        data := <-ch1 // 从ch1通道中读取数据
        fmt.Println("data-->", data)
        fmt.Println("main。。over。。。。")
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    运行结果

    image-20221124182043075

    上面的过程中,先创建了一个chan bool通道.然后启动一条子Goroutine,并循环打印10个数字.然后向通道chn1中写入出入true.然后在主goroutine中,从ch1中读取数据.这一行代码是阻塞的,这意味着在子Goroutine将数据写入到该通道之前,主goroutine将不会执行到下一行代码.因此,可以通过channel实现子goroutine和主goroutine之间的通信.当子goroutine执行完毕前,主goroutine会因为读取ch1中的数据而阻塞.从而保证了子goroutine会先执行完毕.这就消除了对时间的需求.在之前的程序中,要么让主goroutine进入睡眠,一防止主要的Goroutine退出.要么通过WaitGroup来保证子goroutine先执行完毕,主goroutine才结束.

    示例代码: 一下代码加入了睡眠,可以更好的理解channel的阻塞

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch1 := make(chan int)
        done := make(chan bool) // 通道
        go func() {
            fmt.Println("子goroutine执行。。。")
            time.Sleep(3 * time.Second)
            data := <-ch1 // 从通道中读取数据
            fmt.Println("data:", data)
            done <- true
        }()
        // 向通道中写数据。。
        time.Sleep(5 * time.Second)
        ch1 <- 100
    
        <-done
        fmt.Println("main。。over")
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    image-20221124182411241

    如下示例,这个程序将打印一个数字的个位数的平方和.

    package main
    
    import (  
        "fmt"
    )
    
    func calcSquares(number int, squareop chan int) {  
        sum := 0
        for number != 0 {
            digit := number % 10
            sum += digit * digit
            number /= 10
        }
        squareop <- sum
    }
    
    func calcCubes(number int, cubeop chan int) {  
        sum := 0 
        for number != 0 {
            digit := number % 10
            sum += digit * digit * digit
            number /= 10
        }
        cubeop <- sum
    } 
    func main() {  
        number := 589
        sqrch := make(chan int)
        cubech := make(chan int)
        go calcSquares(number, sqrch)
        go calcCubes(number, cubech)
        squares, cubes := <-sqrch, <-cubech
        fmt.Println("Final output", squares + cubes)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    运行结果

    Final output 1536
    
    1
    # 死锁

    使用通道时要考虑的一个重要因素是死锁.如果Goroutine在一个通道上发送数据,那么预计其他的Goroutine应该接收数据.如果这种情况不发生,那么程序将在运行时出现死锁.

    类似地,如果Goroutine正在等待从通道接收数据,那么另一些Goroutine将会在该通道上写入数据,否则程序将会死锁.

    示例代码

    package main
    
    func main() {
        ch := make(chan int)
        ch <- 5
    }
    
    1
    2
    3
    4
    5
    6

    报错:

    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan send]:
    main.main()
        /Users/ruby/go/src/l_goroutine/demo08_chan.go:5 +0x50
    
    1
    2
    3
    4
    5

    在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等.Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言差不多.除了这些基本的同步手段,Go语言还提供了一种新的同步机制: Channel,它在Go语言中是一个像in,float32等的基本类型,一个channel可以认为是一个能够在多个Goroutine之间传递某一类的数据的管道.Go中的channel无论是实现机制还是使用场景都和Java中的BlockingQueue很接近.

    上次更新: 2024/04/09, 16:48:42
    读写锁
    关闭通道和通道上范围循环

    ← 读写锁 关闭通道和通道上范围循环→

    最近更新
    01
    AWS NAT-NetWork-Firwalld配置(一)
    04-09
    02
    AWS NAT-NetWork-Firwalld配置(二)
    04-09
    03
    kubernetes部署minio对象存储
    01-18
    更多文章>
    Theme by Vdoing | Copyright © 2019-2024 Bruce Tao Blog Space | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式