channel

Posted hualou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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会抛出异常
}




以上是关于channel的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 协程Channel 通道 ④ ( Channel 通道的热数据流属性 | Channel 通道关闭过程 | Channel 通道关闭代码示例 )

FFmpeg拼接文件时报错channel element 1.0 is not allocated的分析思路和解决方法

FFmpeg拼接文件时报错channel element 1.0 is not allocated的分析思路和解决方法

FFmpeg拼接文件时报错channel element 1.0 is not allocated的分析思路和解决方法

FFmpeg拼接文件时报错channel element 1.0 is not allocated的分析思路和解决方法

Channel 的死锁