遍历 chan 为 nil 的通道

在线运行open in new window启动 AI 助手open in new window

func main() {
	var ch chan int

    fmt.Println(ch) // <nil>

	// fatal error: all goroutines are asleep - deadlock!
	for n := range ch {
		fmt.Println(n)
	}

    fmt.Println("not goes here")
}

代码定义了一个整型通道 ch,在程序中尝试对 ch 进行读取操作,但是没有对其进行初始化,因此其值为 nil。在程序中打印变量 ch 的值,输出结果为 nil。接着,使用 range 关键字遍历通道 ch,尝试从中读取元素,并将读取到的元素赋值给变量 n。由于通道未初始化,没有与之关联的底层数据结构,因此读取操作会立即导致程序陷入死锁状态,无法继续执行后面的代码。因此,最终输出的结果只有 nil。

需要注意的是,因为程序陷入死锁状态,最后一行打印语句 fmt.Println("not goes here") 永远不会执行。

遍历未关闭的 chan 对象

在线运行open in new window启动 AI 助手open in new window

func main() {
	ch := make(chan int, 3)

	ch <- 1
	ch <- 2
	ch <- 3

	/*
	   1
	   2
	   3
	*/

	for n := range ch {
		fmt.Println(n)
	}

	// fatal error: all goroutines are asleep - deadlock!

	fmt.Println("goes here")
}

代码定义了一个整型缓冲通道 ch,容量为 3,并向其中顺序发送三个元素,分别是 1、2 和 3。然后,使用 range 关键字遍历通道 ch,尝试从中读取元素,并将读取到的元素赋值给变量 n。

在循环中,由于通道已经包含了所有元素,并且没有其他的发送操作可以继续往通道中添加新的元素,因此循环会一直阻塞等待新元素的到来。当通道中的所有元素都被读取完毕后,range 循环会自动结束,因此可以看到输出结果分别是 1、2 和 3。

接着,程序会继续执行打印语句 fmt.Println("goes here"),但是因为之前的 range 循环已经将通道中的所有元素都读取完毕,没有其他的接收操作可以继续从通道中读取新的元素,因此程序会再次陷入死锁状态,无法继续执行后面的代码。因此,最终输出的结果只有 1、2、3。

当 chan 关闭后 range 迭代安全结束

在线运行open in new window启动 AI 助手open in new window

func main() {
	ch := make(chan int, 3)

	ch <- 1
	ch <- 2
	ch <- 3

	go func() {
		time.Sleep(3 * time.Second)
		close(ch)
	}()

	/*
	   1
	   2
	   3
	*/
	for n := range ch {
		fmt.Println(n)
	}

	// fatal error: all goroutines are asleep - deadlock!

	fmt.Println("goes here")
}

代码定义了一个整型缓冲通道 ch,容量为 3,并向其中顺序发送三个元素,分别是 1、2 和 3。接着,使用一个匿名的无缓冲通道来等待 3 秒钟后关闭缓冲通道 ch,这样可以保证 range 循环不会永久阻塞。

然后,使用 range 关键字遍历通道 ch,尝试从中读取元素,并将读取到的元素赋值给变量 n。由于缓冲通道已经包含了所有元素,并且在 3 秒钟后会被关闭,因此循环不会一直阻塞等待新元素的到来。当通道被关闭后,range 循环会自动结束,因此可以看到输出结果分别是 1、2 和 3。

最后,程序会继续执行打印语句 fmt.Println("goes here"),因为在前面已经关闭了通道,因此不会再次阻塞等待新元素的到来,可以正常执行后面的代码。因此,最终输出的结果是 1、2、3 和 goes here。

Last Updated:
Contributors: Bob Wang