zoukankan      html  css  js  c++  java
  • channel

    channel的基本介绍

    • channel的本质是一个数据结构队列
    • 数据是先进先出 FIFO
    • 线程安全,多goroutine访问时,不需要加锁,就是说channel本身是线程安全的
    • channel是由类型的,一个string的channel只能存放string类型数据
    • 无缓冲的channel关闭后,再往外读数据读到的是该管道数据类型的初始值
    • 有缓冲的channel的channel关闭后,如果管道内还有未被读出来的数据,可以继续读出来

    判断管道是否关闭

    if num,ok := <-ch;ok{
        //如果ok为false,定是管道已经关闭了
    }

    使用for range则无需关心关闭的细节,go已经帮我们做好了,一旦检测到关闭,range会自己停止,但是程序的某一处一定要有关闭管道的操作,否则会报死锁

    双向channel可以赋值给单项channel,反过来则不行

    package main
    
    import (
        "fmt"
    )
    
    func send(out chan<- int) {
        out <- 89
        close(out)
    }
    
    func recv(in <-chan int) {
        n := <-in
        fmt.Println("读到", n)
    }
    
    func main() {
        ch := make(chan int)
        go func() {
            send(ch)
        }()
        recv(ch)
    }

    定义/声明

    var intChan chan int //intChan用于存放int数据
    var mapChan chan map[int]string //mapChan用于存放map[int]string类型
    var perChan chan Person
    var perChan2 chan *Person
    1. channel是引用类型
    2. channel必须初始化才能写入数据,即make后才能使用
    3. 管道是由类型的,intChan只能写入整数int
    package main
    import "fmt"
    
    func main(){
        var intChan chan int
        intChan = make(chan int, 3)
        fmt.Printf("intChan的值=%v intChan本身的地址=%p
    ",intChan, &intChan)
        //intChan的值=0xc00007a080 intChan本身的地址=0xc000006028
        intChan<- 10
        num:=211
        //向管道写入数据
        intChan<- num
        //看看管道的长度和cap(容量)
        fmt.Printf("channel len=%v cap=%v 
    ",len(intChan),cap(intChan))
        //从管道中读取数据
        var num2 int
        num2 = <-intChan
        fmt.Println("num2=", num2)
        fmt.Printf("channel len=%v cap=%v 
    ",len(intChan),cap(intChan))
        <-intChan //直接取值不接收
        //在没有下,取完后继续取回报错
    }
    如果使用空interface类型的管道,取出来的结构体数据是 接口类型,需要类型断言才能使用
    package main
    
    import "fmt"
    
    type Cat struct{ Name string }
    
    func main() {
        var catChan chan interface{}
        catChan = make(chan interface{}, 10)
        cat := Cat{"小花猫"}
        catChan <- cat
        newCat := <-catChan //newCat.Name是错的编译的时候编译器会认为newCat是一个接口
        c := newCat.(Cat)   //使用类型断言转换之后可以正常使用
        fmt.Println(c.Name) //小花猫
    }
    
    channel的关闭
    • channel一旦关闭只能读,不能写
    package main
    import "fmt"
    
    func main(){
        intChan := make(chan int, 3)
        intChan<- 100
        intChan<- 200
        close(intChan) //close channel此时只能读不能写
    }
    
    
    channel的遍历
    package main
    
    import "fmt"
    
    func main() {
        intChan := make(chan int, 100)
        for i := 0; i < 100; i++ {
            intChan <- i * 2 //放入100个数据到管段
        }
        //遍历管道要使用fo range的方式去遍历,普通遍历不可以,因为每取一次容量会减少
        close(intChan) // 在遍历时,如果管道没有关闭,则会出现deadlock的错误
        for v := range intChan {
            fmt.Println("v=", v)
        }
    }
    channel支持val,ok:= <-intChan这种方法
    intChan := make(chan int, 100)
    for i := 0; i < 100; i++ {
            intChan <- i * 2 //放入100个数据到管段
        }
    if v, ok := <- intChan;ok{ //成功取到值ok为true否则为false
        fmt.Print(v)
    }
    管道阻塞的机制

    如果编译器运行,发现一个管道只有,没有,则该管道会阻塞

    写管道和读管道的频率不一致,无所谓。

    判断管道是否关闭
    for{
        if v, isClose := <- intChan;isClose{  //通过这种方式判断管道已经关闭
            break
        }
    }
    只读和只写类型的管道
    var wChan chan <-int
    var rChan <-chan int
    传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock,而使用select可以解决从管道取数据的阻塞问题
    select 的用法有点类似 switch 语句,但 select 不会有输入值而且只用于信道操作。select 用于从多个发送或接收信道操作中进行选择,语句会阻塞直到其中有信道可以操作,如果有多个信道可以操作,会随机选择其中一个 case 执行。

    看下例子

    func service1(ch chan string) {
        time.Sleep(2 * time.Second)
        ch <- "from service1"
    }
    func service2(ch chan string) {
        time.Sleep(1 * time.Second)
        ch <- "from service2"
    }
    
    func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        go service1(ch1)
        go service2(ch2)
    
        select {       // 会发送阻塞
        case s1 := <-ch1:
            fmt.Println(s1)
        case s2 := <-ch2:
            fmt.Println(s2)
        }
    }
    
    输出:from service2 上面的例子执行到 select 语句的时候回发生阻塞,main 协程等待一个 case 操作可执行,很明显是 service2 先准备好读取的数据(休眠 1s),所以输出 from service2。 看下在两种操作都准备好的情况:
    func service1(ch chan string) {
        //time.Sleep(2 * time.Second)
        ch <- "from service1"
    }
    func service2(ch chan string) {
        //time.Sleep(1 * time.Second)
        ch <- "from service2"
    }
    
    func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        go service1(ch1)
        go service2(ch2)
    
        time.Sleep(2*time.Second)
        select {
        case s1 := <-ch1:
            fmt.Println(s1)
        case s2 := <-ch2:
            fmt.Println(s2)
        }
    }
    //我们把函数里的延时注释掉,主函数 select 之前加 2s 的延时以等待两个信道的数据准备好,select 会随机选取其中一个 case 执行,所以输出也是随机的。
    与 switch 语句类似,select 也有 default case,是的 select 语句不在阻塞,如果其他信道操作还没有准备好,将会直接执行 default 分支。
    func service1(ch chan string) {
        ch <- "from service1"
    }
    func service2(ch chan string) {
        ch <- "from service2"
    }
    
    func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        go service1(ch1)
        go service2(ch2)
    
        select {         // ch1 ch2 都还没有准备好,直接执行 default 分支
        case s1 := <-ch1:
            fmt.Println(s1)
        case s2 := <-ch2:
            fmt.Println(s2)
        default:
            fmt.Println("no case ok")
        }
    }
    

    添加超时时间

    有时候,我们不希望立即执行 default 语句,而是希望等待一段时间,若这个时间段内还没有可操作的信道,则执行规定的语句。可以在 case 语句后面设置超时时间。

    func service1(ch chan string) {
        time.Sleep(5 * time.Second)
        ch <- "from service1"
    }
    func service2(ch chan string) {
        time.Sleep(3 * time.Second)
        ch <- "from service2"
    }
    
    func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        go service1(ch1)
        go service2(ch2)
    
        select {       // 会发送阻塞
        case s1 := <-ch1:
            fmt.Println(s1)
        case s2 := <-ch2:
            fmt.Println(s2)
        case <-time.After(2*time.Second):     // 等待 2s
            fmt.Println("no case ok")
        }
    }
    
    goroutine中使用recover,必须定义在被协程调用的函数中才行,在main函数中捕获不到
    func test(){
        defer func(){
            if err := recover(); err!=nil{
                fmt.Println("test() 发生错误",err)
            }
        }()
        var myMap map[int]string
        myMap[0] = "golang" //error 这里没有make直接使用map会抛出异常
    }




  • 相关阅读:
    经典问题之生产者-消费者问题——Lock实现
    【转】面试中常见的二叉树题目
    【转】ConcurrentHashMap之实现细节
    【转】java中关键字volatile的作用
    WeakReference 与 SoftReference 区别
    git学习笔记
    android项目笔记整理(3)
    android项目笔记整理(2)
    Android项目笔记整理(1)
    Android实习结束后的阶段性总结
  • 原文地址:https://www.cnblogs.com/hualou/p/12069873.html
Copyright © 2011-2022 走看看