• 8.3 Go channel


     8.3 Go channel

    在Go语言中,关键字go的引入使得Go语言并发编程更加简单而优雅,但是并发编程的复杂性,以及时刻关注并发编程容易出现的问题需要时刻警惕。

    并发编程的难度在于协调,然而协调就必须要交流,那么并发单元之间的通信是最大的问题。

    之前说了在程序中两种并发通信模型:共享数据和消息

    共享数据是指多个并发单元分别保持对同一个数据的引用,实现对数据的共享。

    共享数据可能是内存数据块、磁盘文件、网络数据等。

    Go语言既然一并发为核心,它提供了另一种通信模型,以消息机制而非共享内存作为通信方式

    消息机制:每个并发单元的输入和输入只有一种。
    

    1.1. channel介绍

    Go语言提供的消息通信机制被称为channel

    channel是go语言在语言级别提供的goroutine间的通信方式。
    channel是有类型的,一种channel只能传递一种类型值,这个类型在声明channel时定义。
    

    1.channel本质是一个数据结构(队列)

    2.channel数据遵循FIFO,first in first out。

    3.channel本身是线程安全的,多个goroutine访问时不需要加锁。

    4.一个string类型的channel只能放入string类型数据。

    1.2. 定义channel

    基本语法:
    var 管道名 chan  管道类型
    如:
    var intChan chan int
    var strChan chan string
    var stuChan chan Student //结构体类型channel
    var mapChan chan map[string]string //map类型channel
    var boolChan chan bool //布尔类型channel
    

    注意:

    channel是引用类型
    channel必须make初始化后方可使用
    

    1.3. channel使用

    package main
    
    import "fmt"
    
    func main() {
       //初始化一个可以存放3个int类型数据的管道
       //创建方式一
       //var intChan chan int
       //intChan = make(chan int, 3)
    
       //创建方式二,简短声明创建,如果不make初始化,默认chan是nil
       intChan := make(chan int, 3)
       //查看下channel里面有什么
       fmt.Printf("intChan值:%v    intChan地址:%p
    ", intChan, &intChan)
       fmt.Printf("intChan长度:%v 容量%v
    ", len(intChan), cap(intChan))
    
       //向管道写入数据,只能写入int类型
       intChan <- 10
       //放入第二个数据
       num := 999
       intChan <- num
       //放入第三个数据
       intChan <- 666
       //注意我们初始化chan的容量限制在3,数据不能超过chan的容量
       fmt.Printf("intChan长度:%v 容量%v
    ", len(intChan), cap(intChan))
    
       //从管道中读取数据
       //定义一个变量接收chan的数据,注意先进先出的规律
       res := <-intChan //
       res2 := <-intChan
       fmt.Println(res, res2)
       fmt.Printf("intChan长度:%v 容量%v
    ", len(intChan), cap(intChan))
    
       //数据遵循,有多少读多少,读空的channel也报错
       res3 := <-intChan
       fmt.Println(res3)
       fmt.Printf("intChan长度:%v 容量%v
    ", len(intChan), cap(intChan))
    }
    

    1.4. channel注意事项

    1.channel只能存放声明的数据类型

    2.channel数据放满了,不得再放入,否则panic报错,死锁

    3.取出一个channel数据,少了一个坑,那可以再放入一个数据

    4.数据取空后,不得在取,否则panic报错

    1.5. channel存放数据类型

    1.map类型channel

    package main
    
    import "fmt"
    
    func main() {
        mapChan := make(chan map[string]string, 10) //初始化创建map类型的chan,可以放入map数据
        myMap := make(map[string]string, 10)        //初始化创建map
        myMap["姓名"] = "黑旋风"
        myMap["年纪"] = "28"
    
        mymap2 := make(map[string]string, 10) //初始化创建第二个map
        mymap2["姓名"] = "小妖精"
    
        mapChan <- myMap
        mapChan <- mymap2
    
        fmt.Printf("值:%v 长度:%v 容量:%v
    ", mapChan, len(mapChan), cap(mapChan))
    }
    

    2.结构体类型channel

    package main
    
    import "fmt"
    
    type Student struct {
        Name string
        Age  int
    }
    
    func main() {
        //创建管道
        structChan := make(chan Student,2)
        //创建结构体对象
        s1 := Student{"艾利克斯", 18}
        s2 := Student{"银角大王吧", 19}
        //数据放入管道
        structChan <- s1
        structChan <- s2
    
        //取出管道数据
        stu1 := <-structChan
        stu2 := <-structChan
        fmt.Println(stu1, stu2)
    }
    

    3.存放指针数据的管道

    package main
    
    import "fmt"
    
    type Student struct {
        Name string
        Age  int
    }
    
    func main() {
        //创建管道
        stuChan := make(chan *Student, 10)
        //创建结构体对象
        s1 := Student{"王二狗", 18}
        s2 := Student{"王八犊子", 19}
        //传入结构体对象到管道
        stuChan <- &s1
        stuChan <- &s2
        //取出管道的值
        stu1 := <-stuChan
        stu2 := <-stuChan
        fmt.Printf("%v %v
    ", stu1, stu2)
    }
    

    4.创建可以接收任意数据类型的channel,注意,空接口可以存放任意数据类型。

    package main
    
    import "fmt"
    
    type Student struct {
        Name string
        Age  int
    }
    
    func main() {
        allChan := make(chan interface{}, 10)
        allChan <- 123
        allChan <- "我是字符串"
        res1 := <-allChan
        res2 := <-allChan
        fmt.Printf("res1值:%v 类型:%T
    ", res1, res1)
        fmt.Printf("res2值:%v 类型:%T
    ", res2, res2)
    
        //定义结构体
        s1 := Student{"王麻子", 11}
        allChan <- s1
        //取出结构体,查看类型
        newStu := <-allChan
        fmt.Printf("%T %v
    ", newStu, newStu)
        //newStu此时无法直接使用,必须类型断言后使用
        r := newStu.(Student) //断言它是结构体类型
        fmt.Printf("r值:%v 类型:%T
    ", r, r)
    }
    

    1.6. channel缓冲区

    初始化channel的方式是make函数。

    ch :=make(chan int) //无缓冲区的管道
    ch2:=make(chan int,0)//无缓冲区的管道
    ch3:=make(chan int,10)//有缓冲区的管道,且容量是10
    

    如图所示:

    两个协程,传递数据,无缓存的管道,就是不可以保存数据的通道。

    无缓冲的管道:在管道接受数据前,不保存任何值。
    无缓存的管道:这种类型的管道要求两个goroutine同时接、收值。
    如果goroutine没同时准备好,会导致有一方阻塞状态。
    这种管道进行接、收值的行为,本身是同步状态。
    

    无缓冲管道案例

    package main
    
    import (
        "fmt"
        "time"
    )
    
    //运动员1
    func write(ch chan int) {
        for i := 0; i < 10; i++ {
            ch <- i
            fmt.Printf("棒子%d号来了
    ", i)
            //每秒塞一个棒子
            time.Sleep(time.Second)
            fmt.Println("-------------------")
        }
    }
    
    //运动员2
    func read(ch chan int) {
        for v := range ch {
            fmt.Printf("拿到了棒子%d号
    ", v)
        }
    }
    
    func main() {
        intChan := make(chan int, 0)
        go write(intChan)
        go read(intChan)
        time.Sleep(10 * time.Second)
    }
    

    有缓冲的管道

    有缓冲的管道:在被接收前能存储一个或多个值的通道。
    这种管道不要求goroutine必须同时发送和接收。
    只有管道中没有要接收的值,接收动作才会阻塞。
    只有管道塞满了数据,发送动作才会阻塞。
    

    示意图

    package main
    
    import (
        "fmt"
        "time"
    )
    
    //运动员1
    func write(ch chan int) {
        for i := 0; i < 10; i++ {
            ch <- i
            fmt.Printf("棒子%d号来了
    ", i)
            //每秒塞一个棒子
            fmt.Println("-------------------")
        }
    }
    
    //运动员2
    func read(ch chan int) {
        for v := range ch {
            fmt.Printf("拿到了棒子%d号
    ", v)
        }
    }
    
    func main() {
        intChan := make(chan int, 5)
        go write(intChan)
        go read(intChan)
        time.Sleep(2 * time.Second)
    }
    

    1.7. 关闭channel

    使用内置函数close可以关闭channel。

    channel关闭后,就不能再向channel写入数据了,但是仍然可以从channel读取数据。

    package main
    
    import "fmt"
    
    func main() {
        intChan := make(chan int, 3)
        intChan <- 123
        intChan <- 456
        close(intChan) //通道关闭,游客禁止入内,数据也无法写入
        //intChan<-123  //此处已经不能加载 panic: send on closed channel
    
        fmt.Println("管道已经关闭,在管道中的游客请尽快出来")
        n1 := <-intChan
        n2 := <-intChan
        fmt.Println(n1, n2)
    }
    

    遍历channel的方式

    package main
    
    import "fmt"
    
    func main() {
        intChan := make(chan int, 100)
        for i := 0; i < 100; i++ {
            intChan <- i * i //循环写入一百个数据
        }
    
        //遍历方式1,取出管道值
        //for i := 0; i < len(intChan); i++ {
        //    fmt.Println(i)
        //}
    
        //遍历方式2,for range,参数只有一个
        //在遍历管道时,必须先关闭,否则死锁报错
        close(intChan)
        for v := range intChan {
            fmt.Printf("类型:%T 值:%d
    ", v, v)
        }
    }
    

    1.8. 单向channel

    默认情况下,channel是双向的,既可以写入数据,也可以读取数据。

    但是有些场景下,管道当做参数传递,且希望仅仅是单向使用(只读,只写),此时可以使用单向管道。

    语法

    var ch1 chan int //普通管道
    var ch2 chan <- string //只能写入string类型的数据
    var ch3 <-chan int  //只能读取int类型的数据
    

    提示

    chan <-   表示数据写入channel
    <-chan      表示读取channel中的数据
    

    示例

    1.8.1. 生产消费者模型

    package main
    
    import (
        "fmt"
    )
    
    func Producer(baozi chan<- int) {
        for i := 0; i < 10; i++ {
            baozi <- i //把包子放到管道
            fmt.Printf("三全食品,生产了有毒包子%d号
    ", i)
        }
        //每天就只卖10个包子,卖完为止
        close(baozi)
    }
    
    func Consumer(baozi <-chan int) {
        for baozi := range baozi {
            fmt.Printf("消费者吃了包子%d号.....
    ", baozi)
        }
    }
    
    func main() {
        ch := make(chan int, 10)
        //生产者
        go Producer(ch)
        //消费者
        Consumer(ch)
    }
    

    执行结果图

    案例2

    package main
    
    import "fmt"
    
    //只能写入数据
    func sendData(sendch chan<- int) {
        sendch <- 10
        //只写模式下,不能读取操作
        //invalid operation: <-sendch (receive from send-only type chan<- int)
        //<-sendch
    }
    
    //只能读取数据
    func readData(sendch <-chan int) {
        //sendch <- 10
        data := <-sendch
        fmt.Println(data)
    }
    
    func main() {
        chnl := make(chan int)
        go sendData(chnl) //开启协程,写入数据到管道
        readData(chnl)
    }
  • 相关阅读:
    c++ 判断是元音还是辅音
    c++示例 计算器
    c++容器 算法 迭代
    获取 Django版本号的两种方式
    ImportError: No module named pytz
    Nginx配置proxy_pass转发的/路径问题
    VIM复制粘贴大全!
    SSL协议与数字证书原理
    分析支付宝客户端的插件机制
    PHP session的实现原理
  • 原文地址:https://www.cnblogs.com/open-yang/p/11256905.html
Copyright © 2020-2023  润新知