zoukankan      html  css  js  c++  java
  • Go 信道Channel

    信道(Channel)

    信道(Channel)可以被认为是协程之间通信的管道。数据可以从信道的一端发送并在另一端接收。

    默认为同步模式,需要发送和接收配对。否则会被阻塞,直到另外的信道准备好后被唤醒。

    信道分为无缓冲信道和有缓冲信道
    无缓冲信道:信道是同步的,接收前没有能力保存任何值。这种类型的信道只有发送和接收同时准备好,才能进行下次信道的操作,否则会导致阻塞。
    有缓冲信道:信道是异步的,是一种在被创建时就被开辟了能存储一个或者多个值的信道。这种类型并不要求发送与接收同时进行。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

    发送与接收默认是阻塞的(同步的)。这是什么意思?当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。
    与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。
    信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。

    信道声明

    var ch chan T

    我们声明了一个T类型的名称叫做ch的信道
    信道的 0 值为 nil。我们需要通过内置函数 make 来创建一个信道,就像创建 map 和 slice 一样。

    无缓冲信道

    ch := make(chan T)

    有缓冲信道

    ch := make(chan T,cap)

    (1)信道的创建

         //内置类型channel
        var a chan int
        if a == nil {
            a = make(chan int)
            fmt.Printf("%T
    ", a) //chan int
        }
        //自定义类型channel
        var p chan person
        if p == nil {
            p = make(chan person) //chan main.person
            fmt.Printf("%T
    ", p)
        }

    (2)通过信道发送和接收数据

    注意:发送和接收默认是阻塞的

    data := <- a // 从信道 a 中读取数据并将读取的值赋值给变量 data 。
    a <- data // 向信道 a 中写入数据。
    package main
    import (
        "fmt"
        "time"
    )
    func hello(done chan bool) {
        fmt.Println("hello go routine is going to sleep")
        time.Sleep(4 * time.Second)
        //只有写数据后才能继续执行
        done <- true
        fmt.Println("hello go routine awake and going to write to done")
    }
    func main() {
        done := make(chan bool)
        go hello(done)
        <-done
        fmt.Println("Main received data")
    }

    (4)死锁

    当 Go 协程给一个信道发送数据时,照理说会有其他 Go 协程来接收数据。如果没有的话,程序就会在运行时触发 panic,形成死锁。

    同理,当有 Go 协程等着从一个信道接收数据时,我们期望其他的 Go 协程会向该信道写入数据,要不然程序就会触发 panic。

    package main
    
    func main() {  
        ch := make(chan int)
        ch <- 5
    }

    (5)单向信道与双向信道

    双向信道:即通过信道既能发送数据,又能接收数据。
    单向信道:信道只能发送或者接收数据。

    单向信道:

    package main
    
    import "fmt"
    
    func sendData(sendch chan<- int) {  
        sendch <- 10
    }
    
    func main() {  
        sendch := make(chan<- int)    
        go sendData(sendch)
        fmt.Println(<-sendch)
    }

    chan<- int 定义了唯送信道,因为箭头指向了 chan。我们在主函数中试图通过唯送信道接收数据,于是编译器报错:
    invalid operation: <-sendch (receive from send-only type chan<- int)
    一切都很顺利,只不过一个不能读取数据的唯送信道究竟有什么意义呢?
    这就需要用到信道转换(Channel Conversion)了。把一个双向信道转换成唯送信道或者唯收(Receive Only)信道都是行得通的,但是反过来就不行。

    package main
    
    import "fmt"
    
    func sendData(sendch chan<- int) {  
        sendch <- 10
    }
    func main() {  
        cha1 := make(chan int)    
        go sendData(cha1)
        fmt.Println(<-cha1)
    }

    我们创建了一个双向信道 cha1。 cha1 作为参数传递给了 sendData 协程。函数 sendData 里的参数 sendch chan<- int 把 cha1 转换为一个唯送信道。
    于是该信道在 sendData 协程里是一个唯送信道,而在 Go 主协程里是一个双向信道。该程序最终打印输出 10。

    (6)关闭信道

    数据发送方可以关闭信道,通知接收方这个信道不再有数据发送过来。

    当从信道接收数据时,接收方可以多用一个变量来检查信道是否已经关闭。

    v, ok := <- ch
    上面的语句里,如果成功接收信道所发送的数据,那么 ok 等于 true。而如果 ok 等于 false,说明我们试图读取一个关闭的通道。
    从关闭的信道读取到的值会是该信道类型的零值。例如,当信道是一个 int 类型的信道时,那么从关闭的信道读取的值将会是 0。

    package main
    
    import (  
        "fmt"
    )
    
    func producer(chnl chan int) {  
        for i := 0; i < 10; i++ {
            chnl <- i
        }    close(chnl)
    }
    
    func main() {  
        ch := make(chan int)    
        go producer(ch)    
        for {
            v, ok := <-ch        
            if ok == false {            
                break
            }
            fmt.Println("Received ", v, ok)
        }
    }

    在上述的程序中,producer 协程会从 0 到 9 写入信道 chn1,然后关闭该信道。
    主函数有一个无限的 for 循环,使用变量 ok检查信道是否已经关闭。如果 ok 等于 false,说明信道已经关闭,于是退出 for 循环。
    如果 ok 等于 true,会打印出接收到的值和 ok 的值。

    (7)遍历信道

    信道支持range for遍历

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func producer(chnl chan int) {
        defer close(chnl) //程序执行结束关闭信道
        for i := 0; i < 10; i++ {
            time.Sleep(300 * time.Millisecond) //一秒写一次
            chnl <- i                          //写操作
        }
    }
    func main() {
        ch := make(chan int)
        go producer(ch)
        //接收ch信道中的数据,直到该信道关闭。
        for v := range ch {
            fmt.Println(v)
        }
    } 

    也可以自定for循环遍历信道

    for {
            v, ok := <-ch //读操作
            fmt.Println(v, ok)
            if ok == false { //当读取不到数据跳出循环
                break
            }
        }
    

     

  • 相关阅读:
    20165223 week6测试错题总结
    20165223《Java程序设计》第七周Java学习总结
    20165207 第八周学习总结
    2017-2018-2 20165207实验二《Java面向对象程序设计》实验报告
    20165207 第七周学习总结
    20165207 第六周学习总结
    20165207 实验一 Java开发环境的熟悉
    20165207 第五周学习总结
    20165207 第四周学习总结
    20165207 第三周学习总结
  • 原文地址:https://www.cnblogs.com/-wenli/p/11817266.html
Copyright © 2011-2022 走看看