当前位置:首页 > Golang实例教程 > 正文内容

Go channel使用注意事项

3个月前 (07-11)Golang实例教程256

网上关于channel的使用有很多介绍,这里不在阐述,这里主要是记录下开发中,可能存在使用channel不当造成的问题总结下,

说道这里,还是总结下channel的几个特性吧:

  • 给一个 空 channel发送数据,会造成永远阻塞

  • 从一个 空 channel接收数据,会造成永远阻塞

  • 给一个已经关闭的channel发送数据,会引起 panic

  • 从一个已经关闭的channel接收数据,如果缓冲区中为空,则返回一个零值

  • 无缓冲的channel是同步的,有缓冲的channel是异步的

Channel定义

var chn chan int // 声明一个传递类型为int的管道
var m map[string]chan bool // 声明一个map,元素是bool型的channel
chn := make(chan int) // 定义语法,定义需要使用内置函数make()即可,下面这行代码是声明+定义一个整型管道
chn := make(chan int, 10)  // 事先定义好管道的size,下面这行代码定义管道的size为10

// 由管道中读写数据,<-操作符是与最左边的chan优先结合的
// 向管道中写入一个数据,在此需要注意:向管道中写入数据通常会导致程序阻塞,直到有
// 其他goroutine从这个管道中读取数据
chn <- value
// 读取数据,注意:如果管道中没有数据,那么从管道中读取数据会导致程序阻塞,直到有数据
value := <-chn

// 单向管道
var chn1 chan<- float64 // 只能向里面写入float64的数据,不能读取
var chn2 <-chan int // 只能读取int型数据,注意chan在箭头的右边,且chan 后跟了类型,一定是只读channel,如本处chn2

// 关闭channel,直接调用close()即可
close(ch)
// 判断ch是否关闭,判断ok的值,如果是false,则说明已经关闭(关闭的话读取是不会阻塞的)
v, ok := <-chn

废话不多说,大家看下下面的代码是如何执行的,输出结果是什么?

package main

import "fmt"

func main() {

    var myChan chan int
    myChan = make(chan int, 10) // 定义长度为10的channel
    for i := 0; i < 10; i++ {
        myChan <- i // 写入myChan,写10个值
    }
    close(myChan) //如果不关闭则会deadlock,注释掉本行看看结果输出什么?

    for v := range myChan {
        fmt.Println(v)
    }
}

上面在没有注释close的情况下正常输出。否则fatal error: all goroutines are asleep - deadlock!。通过本例子我们知道了:

channel 在使用 for–range 的方式进行遍历的时候,需注意两个细节:

1:在遍历时,如果 channel 没有关闭,则会出现 deadlock 的错误,因此遍历前需要关闭channel

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

我们接着继续看下面的例子:

package main

import (
    "fmt"
    "time"
)

func main() {
    chn := make(chan int, 1000) //定义一个1000容量的channel
    go func() {
        for i := 0; i < 10; i++ { // 向管道chn发送写入10个数字
        chn <- i
    }
    }()
    go func() { // 开启一个携程 循环从chn中获取值
        for {
        i, ok := <-chn
        if !ok { // 取值为空或零
            fmt.Println("chn已经关闭")
          return
        }
        fmt.Println("i = ", i)
        }
    }()
    close(chn) // 这里直接关闭了chn
    fmt.Println("sucess")
    time.Sleep(time.Minute * 1) // 等待1分钟让携程全部执行完毕
}

上面代码最大的问题在哪里? 很明显。代码中开启了两个go携程,第一个写数据,第二个读取数据,开完两个goroutine之后main主携程直接close掉channel,

然后直接输出打印:success。

但是往已经关闭的channel写入数据会panic的。所以上面的结果输出:

sucess
chn已经关闭
panic: send on closed channel

goroutine 6 [running]:
main.main.func1()
        /media/uos/G/web/demo-go/main.go:12 +0x36
created by main.main
        /media/uos/G/web/demo-go/main.go:10 +0x6b
exit status 2

通过上面在2个携程中抛出panic来看,咋们如何解决协程中出现这种 panic导致程序崩溃问题?其实在

goroutine 中使用 recover,就可以解决协程中出现 panic。下面我起了一个协程,但是这个协程出现了panic,如果没有甫获这个panic,

就会造成整个程序崩溃,这时可以在goroutine中使用ecover来捕获panic,进行处理,这样即使这个协程发生的问题,

但是主线程仍然不受影响,可以继续执行。

package main

import (
    "fmt"
    "time"
)

func sayTest() {
    for i := 0; i < 10; i++ {
        time.Sleep(time.Second)
        fmt.Println("here is a test")
    }
}

// 函数
func catchPanic() {
    //这里我们可以使用defer + recover
    defer func() {
        //捕获catchPanic抛出的panic
        if err := recover(); err != nil {
            fmt.Println("catchPanic() 发生了错误", err)
        }
    }()

    var myMap map[string]string //定义了一个map
    myMap["code"] = "golang"    //此行error 因为没有make初始化分配内存地址
}

func main() {
    // 开启两个携程
    go sayTest()
    go catchPanic()

    time.Sleep(time.Second * 2) // 没耐心等待携程执行完毕,提前看看结果,毕竟sayTest sleep了10s
}

输出:

catchPanic() 发生了错误 assignment to entry in nil map
here is a test
here is a test

在介绍一个使用select 解决从管道取数据的阻塞问题:

package main

import (
    "fmt"
)

// 使用select解决从管道取数据的阻塞问题
func main() {

    intChn := make(chan int, 10) // 定义一个管道10个数据int
    for i := 0; i < 10; i++ {
        intChn <- i // 往intChn写入10个数字
    }

    strChn := make(chan string, 5) // 定义一个管道 5个数据string
    for i := 0; i < 5; i++ {
        strChn <- "我是Gofans" + fmt.Sprintf("%d", i) // Sprintf返回的string,字符串可以直接连接
    }

    //使用传统的方法在遍历管道时,如果不关闭会阻塞而导致deadlock,上面的示例中有讲到这块
    //问题,在实际开发中,可能我们不好确定什么时候关闭该管道
    //可以使用select的方式可以解决
    for {
        select {
        //注意这里如果intChn一直没有关闭,不会一直阻塞而deadlock
        //case会自动到下一个case匹配
        case v := <-intChn:
            fmt.Printf("从initChn读取的数据%d\n", v)
        case v := <-strChn:
            fmt.Printf("从strChn读取的数据%s\n", v)
        default:
            fmt.Printf("两个channel都取不到数据了,到此结束吧\n")
            return
        }
    }
}

上面就介绍这么多了,欢迎广大老铁留言补充。

    扫描二维码推送至手机访问。

    版权声明:本文由周伯通的博客发布,如需转载请注明出处。

    本文链接:http://zhoubotong.site/post/58.html

    分享给朋友:
    返回列表

    上一篇:hello world

    没有最新的文章了...

    相关文章

    hello world

        之前写的一些Go相关的文章比较分散,一直以来想系统性地写一个工作中实践的心得教程,在这里决定还是静下心,坚持写完一个完整的系列教程。也希望广大博友能提出...

    发表评论

    访客

    看不清,换一张

    ◎欢迎参与讨论,请在这里发表您的看法和观点。