关于Go语言的非main函数退出后,其子协程的是否退出这件事
你知道的
我们都知道,如果在mian中启动一个协程,如果main结束的时候,这个协程还有结束的话,系统也会强制关闭这个协程
最简单的例子如下:
func main() {
go func() {
time.Sleep(time.Second)
fmt.Println("THIS IS KID")
}()
fmt.Println("THIS IS MAIN")
}
输出:
THIS IS MAIN
但是,你在编程的时候,有没有想过:
假如你有一个非main的函数,里面也启动了一个协程,如果这个非main结束的话,里面的那个子协程会结束吗?
你可以先自己想一下,先别看下面的内容,欢迎留言!!!
实验验证
再看之前,先想明白问题是什么?
那么我们来实验验证一下,不就得了。
直接上最简单的代码:
func test() int {
go func() { //子协程
time.Sleep(time.Second*2)
fmt.Println("非main函数的子协程结束")
}()
fmt.Println("非main函数结束")
return 1
}
func main() {
fmt.Println("非main函数返回值为", test())
time.Sleep(time.Second*5)
fmt.Println("main函数结束")
}
输出:
非main函数结束
非main函数返回值为 1
非main函数的子协程结束
main函数结束
由于test中的子协程延迟了2s,所以test函数会先结束,故打印出了“非main函数结束”和“非main函数返回值为 1”,因为我们在main中加了5s的延迟(为了避免main退出带来的影响),故肯定不会早于test子协程退出。这个时候打印出了“非main函数的子协程结束”,说明test的子协程并没有因为test的结束而结束,在之后,打印出“main函数结束”,main函数结束
由此,我们可以的得出结论:
非main函数退出后,其子协程是不会退出的
白话:A不是main程的情况下,在A程里开启B程,A程执行完,A程return之后,B程不受影响,不会挂掉。
所有的协程都是同一级别的,别管在哪个地方启动的协程(当然,主main退出的话,全部的协程也就退出了)
我们再来看一个例子:结合上通道
直接上代码
func main() {
fmt.Println("非main函数返回值为", mirroredQuery())
time.Sleep(time.Second*5)
fmt.Println("main函数结束")
}
func mirroredQuery() string {
responses := make(chan string)
go func() { responses <- request("A",3)
fmt.Println("A协程结束")
}()
go func() { responses <- request("B",1)
fmt.Println("B协程结束")
}()
return <-responses
}
func request(hostname string,n int) (response string) {
time.Sleep(time.Second*time.Duration(n))
return hostname
}
输出:
B协程结束
非main函数返回值为 B
main函数结束
看到这里,你是不是又有疑问了?上边他娘的不是刚证明了吗,怎么这里又不好使了呢?
这里呢,都是因为这个responses := make(chan string)搞的鬼
我们可以看到,它是一个无缓冲通道,什么意思,也就是说,之后这个通道在接收的时候,我们才能写入,或者说,之后我们写入了的时候,它必须在等着接收,不能像有缓冲通道那样,我放上就行了,然后,你有空了,你就来取。
这样的话,”B协程结束“和”非main函数返回值为 B“先输出
注意,此时我们的A协程是没有死的,因为main又没结束。而是卡在了responses <- request(“A”,3),就是因为通道是无缓冲的,且mirroredQuery函数已经结束了,不会再去执行<-responses这个读的操作, 所以A协程就一直阻塞在responses <- 这个地方,永远动不了了
这就造成了goroutines泄露的问题,是Go语言新手经常遇到的问题,与垃圾回收不同,泄露的goroutines并不会被自动回收。
在这里,可以改为带缓冲的通道,使得数据能写入到chan,保证协程可以结束
responses := make(chan string,3)
输出:
B协程结束
非main函数返回值为 B
A协程结束
main函数结束