all goroutines are asleep - deadlock!

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/benben_2015/article/details/84842486

默认情况下,通信是同步且无缓冲的。这种特性导致通道的发送/接收操作,在对方准备好之前是阻塞的。

  • 对于同一个通道,发送操作在接收者准备好之前是阻塞的。如果通道中的数据无人接收,就无法再给通道传入其他数据。新的输入无法在通道非空的情况下传入,所以发送操作会等待channel再次变为可用状态,即通道值被接收后。
  • 对于同一个通道,接收操作是阻塞的,直到发送者可用。如果通道中没有数据,接收者就阻塞了。

示例一

package main

import "fmt"

func main() {
	ch1 := make(chan string)
	ch1 <- "hello world"
	fmt.Println(<-ch1)
}

执行后,会报这样的错误。

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    D:/gotest/main.go:7 +0x5c
exit status 2

原因分析

在代码的第7行,我们给通道ch1中传入一个值"hello world"。但是根据我们之前讲的,对于同一无缓冲通道,在接收者未准备好之前,发送操作是阻塞的。而此处的通道ch1就是缺少一个配对的接收者,因此造成了死锁。
解决上面问题的方式有两种:第一种添加配对的接收者;第二种将默认的通道替换成缓冲通道。
方法一

package main

import "fmt"

func main() {
	ch1 := make(chan string)
	go func() {
		ch1 <- "hello world"
	}()
	fmt.Println(<-ch1)
}

在主函数中启用了一个goroutine,匿名函数用来发送数据,而在main()函数中接收通道中的数据。
方法二

package main

import "fmt"

func main() {
	ch1 := make(chan string, 1)
	ch1 <- "hello world"
	fmt.Println(<-ch1)
}

此时的ch1通道可以称为缓冲通道,在缓冲满载(缓冲被全部使用)之前,给一个带缓冲的通道发送数据是不会阻塞的,而从通道读取数据也不会阻塞,直到缓冲空了。定义方法如:ch:=make(chan type, value)
这个value表示缓冲容量,它的大小和类型无关,所以可以给一些通道设置不同的容量,只要它们拥有相同的元素类型。
内置的cap函数可以返回缓冲区的容量,如果容量大于0,通道就是异步的了。缓冲满载(发送)或变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收。如果容量是0或者未设置,通信仅在收发双方准备好的情况下才可以成功。

这种异步channel可以减少排队阻塞,在你的请求激增的时候表现得更好,更具伸缩性。

示例二

现在思考这样的问题,如果发送多个值,我们如何接收。例如:

package main

import (
	"fmt"
)

func main() {
	ch1 := make(chan string)
	go func() {
		fmt.Println(<-ch1)
	}()
	ch1 <- "hello world"
	ch1 <- "hello China"
}

上述代码执行后,结果为:

hello world
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
    D:/gotest/main.go:13 +0x97

出现这样的结果是因为通道实际上是类型化消息的队列,它是先进先出(FIFO)的结构,可以保证发送给它们的元素的顺序。所以上面代码只取出了第一次传的值,即"hello world",而第二次传入的值没有一个配对的接收者来接收,因此就出现了deadlock。那么将代码变成这样,又会是什么结果呢?

package main

import "fmt"

func main() {
	ch1 := make(chan string)
	go func() {
		ch1 <- "hello world"
		ch1 <- "hello China"
	}()
	fmt.Println(<-ch1)
}

上面代码执行后结果为:hello world,但却没有报错,这又是为什么呢?

原因分析

信号量模式:协程通过在通道中放置一个值来处理结束的信号,main协程等待,直到从通道中获取到值。
上面的程序中有两个函数:main()函数和一个发送操作的匿名函数。它们按独立的处理单元按顺序启动,然后开始并行运行。通常情况下,由于main()函数不会等待其他非main协程的结束。但是此处的ch1相当于信号量,通过在ch1中放置一个值来处理结束的信号。main()协程等待<-ch,直到从中获取到值,然后程序直接退出。根本没有执行到继续往通道中传入"hello China",也就不会出现deadlock的出现。

示例三

package main

import "fmt"

func main() {
	ch1 := make(chan string)
	go func() {
		ch1 <- "hello world"
		ch1 <- "hello China"
	}()
	for {
		fmt.Println(<-ch1)
	}
}

执行后,会报这样的错误。

hello world
hello China
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
    D:/gotest/main.go:12 +0x83
exit status 2

出现上面的结果是因为for循环一直在获取通道中的值,但是在读取完hello worldhello China后,通道中没有新的值传入,这样接收者就阻塞了。
那么如何判断通道是否阻塞,如何关闭通道,后面的文章将会陆续探讨。

参考文章

  1. 协程间的信道

猜你喜欢

转载自blog.csdn.net/benben_2015/article/details/84842486
ALL