all goroutines are asleep - deadlock!

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

  • 对于同一个通道,发送操作在接收者准备好之前是阻塞的。如果通道中的数据无人接收,就无法再给通道传入其他数据。新的输入无法在通道非空的情况下传入,所以发送操作会等待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. 协程间的信道
  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
好的,以下是三道题的详细代码实现: 1. 自定义sendData()函数并发发送数据 ```go package main import ( "fmt" ) func sendData(ch chan string) { ch <- "beijing" ch <- "shanghai" ch <- "hangzhou" close(ch) } func main() { ch := make(chan string) go sendData(ch) for data := range ch { fmt.Println(data) } } ``` 在该代码中,我们定义了sendData()函数,该函数会并发向通道中发送三个字符串,并关闭通道。在主函数中,我们创建了一个通道,然后开启一个协程执行sendData()函数。主函数中通过range循环从通道中获取并打印数据,由于通道在sendData()函数中被关闭,所以range循环可以正常结束。 2. 并发售票功能 ```go package main import ( "fmt" "sync" ) var ( tickets = 10 // 总票数 wg sync.WaitGroup mutex sync.Mutex // 互斥锁 ) func sellTicket(ch chan int, id int) { defer wg.Done() for { mutex.Lock() if tickets > 0 { tickets-- fmt.Printf("售票员%d售出一张票,剩余%d张票\n", id, tickets) mutex.Unlock() ch <- 1 // 向通道中发送数据 } else { mutex.Unlock() fmt.Printf("售票员%d售票完成\n", id) break // 售票完成,跳出循环 } } } func main() { ch := make(chan int) for i := 1; i <= 3; i++ { wg.Add(1) go sellTicket(ch, i) } // 等待所有售票员完成售票 go func() { wg.Wait() close(ch) }() // 从通道中接收数据,直到通道关闭 for range ch { } } ``` 在该代码中,我们定义了sellTicket()函数,该函数在一个for循环中不断地售出票。我们使用互斥锁来保证同时只有一个售票员能够售出票,然后把售票的结果通过通道发送出去。在主函数中,我们创建了一个长度为0的通道,并开启了三个协程代表三个售票员来售票。最后,我们通过一个匿名协程来等待售票员售票完成并关闭通道,然后通过range循环从通道中获取并忽略数据,直到通道关闭。 3. Web程序并发售票功能 ```go package main import ( "fmt" "net/http" "sync" ) var ( tickets = 10 // 总票数 mutex sync.Mutex // 互斥锁 ) func sellTicket(w http.ResponseWriter, r *http.Request) { mutex.Lock() defer mutex.Unlock() if tickets > 0 { tickets-- fmt.Fprintf(w, "售出一张票,剩余%d张票\n", tickets) } else { fmt.Fprint(w, "票已售完\n") } } func main() { http.HandleFunc("/sell", sellTicket) err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("ListenAndServe: ", err) } } ``` 在该代码中,我们定义了一个sellTicket()函数,该函数会在请求时售出一张票,如果票已售完则返回"票已售完"的信息。我们使用互斥锁来保证同时只有一个请求能够访问售票逻辑。在主函数中,我们使用http包来监听8080端口,并将sellTicket()函数注册到"/sell"的路由上。当有请求到达"/sell"时,sellTicket()函数会被调用并售出一张票或返回"票已售完"的信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值