Go channel使用注意事项

原文链接:https://www.zhoubotong.site/post/58.html
网上关于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
		}
	}
}

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

posted @ 2022-07-11 17:17  周伯通之草堂  阅读(159)  评论(0编辑  收藏  举报