go 语言 主程序退出时会进行一些额外工作

主goroutine先执行完,子goroutine居然还可以运行

func main() {
	ch := make(chan int)
	log.Println("===========")
	go func() {
		log.Println("doing in sub goroutine")
		// 主程序向通道发送数据,但此时由于 主 gor 行速度慢
		// 程序还没有执行到 fmt.Println("x in ch is:", <-ch),
		// 通道还没有接收数据,所以此时发生 阻塞
		ch <- 1
		log.Println("done in sub goroutine") // 竞争打印,顺序不定
	}()

	// 模拟执行速度慢
	time.Sleep(1 * time.Second)

	log.Println("doing in main goroutine")
	// 1. 执行到这里时发生,由于子 gor 已经在等待向通道发送数据
	//    所以此处的 从通道接收数据的 <-ch 立即执行
	// 2. 子 gor 被唤酲, 立即向通道发送数据
	// 3. <-ch 开始接收数据

	// 当从通道中取出数据时,数据是通道中复制的,这需要花费一定的时间
	fmt.Println("x in ch is:", <-ch)      // 竞争打印,顺序不定
	log.Println("done in main goroutine") // 竞争打印,顺序不定
}

output:这个比较奇怪,主 先执行完,子居然还可以运行

// 2021/03/04 16:26:35 doing in sub goroutine
// 2021/03/04 16:26:36 doing in main goroutine
// x in ch is: 1
// 2021/03/04 16:26:36 done in main goroutine (主 先执行完)
// 2021/03/04 16:26:36 done in sub goroutine (子居然还可以运行)

关于原因

  1. Stack Overflow上的答案是 在主 gor 的最后一行代码

    log.Println("done in main goroutine") 执行完成后,main 所在的 goroutine 还有一些在 runtime

    需要完成一些扫尾工作,这些扫尾工作会花费非常少的时间,在这段时间里,子 gor 依然可以运行

    所以可以执行 log.Println("done in sub goroutine")

  2. 这也说明了 此次 主 gor 运行的比较慢,给子gor 留下了运行完所有代码的时间

    如果主 gor 运行的特别快,即使扫尾工作需要花费一定的时间,但整体速度依然比

    子 gor 快,则 log.Println("done in sub goroutine") 不能被执行