go 语言通道 channel 阻塞问题

go 语言通道 ( chan )  阻塞机制

非协程环境

简单的讲,在非协程环境 Channel 满了,就阻塞写,Channel空了,就阻塞读。

main 主进程中

如果阻塞发生在 main 主进程里,并且没有其他子协程可以执行,那就可以确定“希望永远等不来”,main 协程会把自己杀掉,报一个 fatal error:deadlock 错误。

下面的代码将报错 :

fatal error: all goroutines are asleep - deadlock!

package main

func main() {
	ch := make(chan int)
	// 写入数据
	ch <- 1
	res := <-ch
	println(res)
	println("主进程结束")
}

原因是向管道添加数据时没有与取出同时执行,改进一下 :

package main
func main() {
	ch := make(chan int, 1)
	// 写入数据
	ch <- 1
	res := <-ch
	println(res)
	println("主进程结束")
}

创建一个带有缓冲区的管道,可以缓冲一个数据,这样就不在需要存取同步。

如果在取出之前再存入一条数据就会报错 :

package main
func main() {
	ch := make(chan int, 1)
	// 写入数据
	ch <- 1
	// 此处会造成阻塞
	ch <- 2
	res := <-ch
	println(res)
	println("主进程结束")
}

协程环境

如果阻塞发生在子协程里,就不会发生死锁,因为至少 main 协程是一个值得等待的“希望”,会一直等下去。

go 的协程很聪明,阻塞之后它就主动交出  cpu,相当于调用 runtime.Gosched(),让其他协程去执行,希望其他协程能帮自己解除阻塞(当然是通过读写管道的方式)。

package main

func main() {
	ch := make(chan int)
	// 开启一个协程
	go func() {
		// 存入数据
		// 此处会临时阻塞,等待有取出数据动作与填充动作匹配
		ch <- 1
	}()
	// 取出数据
	res := <-ch
	println(res)
	println("主进程结束")
}

阻塞总结

读一个空管道或写一个缓冲已经满的管道,到底会发生什么行为,需要分情况讨论:

1. 发生在非 main 协程里,会阻塞。

2. 发生在 main 协程里 :

2.1 没有其他非 main 协程可以执行,报 fatal error: all goroutines are asleep - deadlock!

2.2 有其他非main协程可以执行,则 main 协程会让他们先执行。

2.2.1 非 main 协在程执行过程中,帮 main 协程解除了阻塞。

2.2.2 非 main 协执行结束后,依然没有帮 main 协程解除阻塞,则 main 协程报 fatal error: all goroutines are asleep - deadlock!