• [go学习笔记.第十四章.协程和管道] 2.管道


    1.为什么需要管道

     

    (1).前面使用全局变量加锁同步来解决 goroutine 的通讯,但不完美

    (2).主线程在等待所有goroutine 全部完成的时间很难确定,这里设置 10 秒,仅仅是估算

    (3).如果主线程休眠时间长了,会加长等待时间,如果等待时间短了,可能还有goroutine 处于工作状态,这时也会随主线程的退出而销毁

    (4).通过全局变量加锁同步来实现通讯,也并不利于多个协程对全局变量的读写操作

    (5).从上面的分析来看,使用一种新的通讯机制是非常必要的,这个新的通讯机制就是管道-channel

    2.channel的基本介绍

    (1).channel本质就是一个数据结构一队列【 示意图如下

    (2).数据是先进先出 【 FIFO:first in first out】

    (3).线程安全,多goroutine 访间时,不需要加锁,就是说channel本身就是线程安全的

    (4).channel是有类型的,一个 string 的channel只能存放 string 类型数据

    示意图如下:

     3.定义/声明channel

    var 变量名 chan 数据类型

    eg:

            var intChan chan int  //intChan用于存放int数据

            var mapChan chan map[int]string // mapChan用于存放map[int]string类型

            var perChan chan Person //perChan用于存放Person结构体

            var perChan2 chan *Person  //perChan2用于存放Person结构体指针

            ...

    说明:

            channel是引用类型

            channel必须初始化才能写入数据,即make后才能使用

            channel是有类型的,intChan只能写入整数int

    4.管道的初始化,写入数据到管道,从管道读取数据

    1. package main
    2. import (
    3. "fmt"
    4. )
    5. func main() {
    6. //演示管道的使用
    7. //1.创建一个可以存放3个int的管道
    8. var intChan chan int
    9. intChan = make(chan int, 3)
    10. //2.看看intChan是什么
    11. //intChan 的值:0xc0000d8080, inChan本身的地址:0xc0000d2018
    12. fmt.Printf("intChan 的值:%v, inChan本身的地址:%p\n", intChan, &intChan)
    13. //3.向管道插入数据
    14. intChan<- 1
    15. num := 2
    16. intChan<- num
    17. //注意:当向管道写入数据时,不能超过其容量
    18. //4.查看管道的容量以及长度
    19. //intChan 的长度:2, inChan的容量:3
    20. fmt.Printf("intChan 的长度:%v, inChan的容量:%v\n", len(intChan), cap(intChan))
    21. //5.从管道读取数据
    22. var num2 int
    23. num2 = <-intChan
    24. fmt.Printf("num2=%v\n", num2)//num2=1
    25. //intChan 的长度:1, inChan的容量:3
    26. fmt.Printf("intChan 的长度:%v, inChan的容量:%v\n", len(intChan), cap(intChan))
    27. //6.在没有使用协程的情况下,如果管道的数据已经被全部取出,再取就会报错(deallock)
    28. }

    5.基本的注意事项

    (1).channel中只能存放指定的数据类型

    (2).channel的数据放满后,就不能再放入了

    (3).如果从channel取出数据后,可以继续放入

    (4).在没有使用协程的情况下,如果channel数据取完了,再取,就会报 dead lock

    6.案例

    (1).创建一个intChan,最多可以存放3个int,演示存3个数据到intChan,然后再取出这三个int

    (2).创建一个mapChan,最多可以存放10个map[string][string]的key-value,演示写入和读取

    (3).创建一个catChan,最多可以存放10个Cat结构体变量,演示写入和读取

    (4).创建一个catChan2,最多可以存放10个*Cat结构体变量,演示写入和读取

    (5).创建一个allChan,最多可以存放10个任意数据类型变量,演示写入和读取的用法

    (6).案例:      

            1).创建一个 Person 结构体 (Name , Age , Address )

            2).使用 rand 方法配合随机创建10个Person 实例,并放入到 channel 中

            3).遍历channel ,将各个 Person 实例的信息显示在终端

    1. package main
    2. import (
    3. "fmt"
    4. "math/rand"
    5. "time"
    6. "strconv"
    7. )
    8. type Person struct {
    9. Name string
    10. Age int
    11. Address string
    12. }
    13. func main() {
    14. //初始化随机数
    15. rand.Seed(time.Now().UnixNano())
    16. //创建一个存放10个Person实例的管道
    17. personChan := make(chan Person, 10)
    18. temp := Person{}
    19. for i := 1; i <= 10; i++ {
    20. temp = Person {
    21. Name : "Person" + strconv.Itoa(rand.Intn(100)),
    22. Age : rand.Intn(100),
    23. Address : "成都市华府大道" + strconv.Itoa(rand.Intn(100)) + "号",
    24. }
    25. //把person放入管道中
    26. personChan <- temp
    27. }
    28. //循环从管道中读取数据
    29. lenPersonChan := len(personChan)
    30. // temp1 := Person{}
    31. // var personTemp interface{}
    32. for i := 1; i <= lenPersonChan; i++{
    33. personTemp := <-personChan
    34. fmt.Printf("第%v个person数据的Name=%v,Age=%v, Address=%v\n", i,
    35. personTemp.Name, personTemp.Age, personTemp.Address)
    36. }
    37. }

    7.channel的遍历和关闭

    (1).channel的关闭

    使用内置函数 close可以关闭channel,当 channel 关闭后,就不能再向channel写数据了,但是仍然可以从channel读取数据

    1. package main
    2. import(
    3. "fmt"
    4. )
    5. func main() {
    6. //定义并初始化一个管道
    7. intChan := make(chan int, 3)
    8. intChan <- 100
    9. intChan <- 300
    10. close(intChan) // close 管道,当close后,不能写入数据到intChan
    11. // intChan <- 200 // 写入失败:send on closed channel,因为管道已关闭
    12. fmt.Println("ok")
    13. //当管道关闭后,还是可以读取数据的
    14. n1 := <- intChan
    15. fmt.Printf("n1:=%v\n", n1)
    16. }

    (2).channel的遍历

    channel支持 for-range 的方式进行遍历,请注意两个细节

            1).在遍历时,如果channel没有关闭,则回出现 deadlock的错误

            2).在遍历时,如果 channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历 

    案例演示: 

    1. package main
    2. import(
    3. "fmt"
    4. )
    5. func main() {
    6. //定义并初始化一个管道
    7. intChan := make(chan int, 3)
    8. intChan <- 100
    9. intChan <- 300
    10. close(intChan) // close 管道,当close后,不能写入数据到intChan
    11. // intChan <- 200 // 写入失败:send on closed channel,因为管道已关闭
    12. fmt.Println("ok")
    13. //当管道关闭后,还是可以读取数据的
    14. n1 := <- intChan
    15. fmt.Printf("n1:=%v\n", n1)
    16. //遍历管道
    17. intChan1 := make(chan int, 100)
    18. for i := 0; i < 100; i++ {
    19. intChan1 <- i * 2 //放入100个数到管道
    20. }
    21. //遍历管道时,不能用普通的for循环
    22. //在遍历时,如果没有关闭chan,则会出现deallock错误
    23. ///在遍历时,如果以及已经关闭chan,则会正常遍历数据,遍历完后,退出
    24. close(intChan1)
    25. for v := range intChan1 {
    26. fmt.Println("v=", v)
    27. }
    28. }

    [上一节]go学习笔记.第十四章.协程和管道] 1.协程的引入,调度模型以及运行cpu数目,协程资源竞争问题

    [下一节]go学习笔记.第十四章.协程和管道] 3.协程配合管道案例以及管道的注意事项和使用细节 

  • 相关阅读:
    61张图,图解Spring事务,拆解底层源码
    [Power Query] 删除错误/空值
    x64 番外篇——知识铺垫
    分布式(一致性协议)之领导人选举( DotNext.Net.Cluster 实现Raft 选举 )
    S7-1200PLC和LED电子看板通信(TCP/IP)
    The Sandbox Alpha 第三季游戏体验推荐|《爱是永恒》
    多云系列|10个关键的多云战略:简介
    基于模糊神经网络的时间序列预测(以hopkinsirandeath数据集为例,MATLAB)
    函数栈帧的创建和销毁
    【Hadoop】- MapReduce & YARN 初体验[9]
  • 原文地址:https://blog.csdn.net/zhoupenghui168/article/details/127811925