发现问题
小白一枚,最近在入门学习Golang,在学到channel时,发现总会遇到死锁的问题:
fatal error: all goroutines are asleep - deadlock!
原理解读
通过查找相关资料发现,channel本身是用于不同协程间通信的,一般一个协程作为生产者,另一个作为消费者,遵循下面两个原则:
- 当上一次生产到channel的数据未被消费时,生产者继续生产将会阻塞
- 当生产者停止生产时,消费者继续消费将会阻塞
所以,当我们写的程序生产和消费的数量不对等时,便会发生阻塞造成死锁
代码示例
生产者阻塞:
生产4条消息,只消费3条
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func producer(ch chan<- int) {
defer wg.Done()
for i := 1; i <= 4; i++ {
ch <- i
}
}
func consumer(ch <-chan int){
defer wg.Done()
for i := 1; i <= 3; i++ {
fmt.Printf("result: %d\n", <-ch)
}
}
func main() {
ch := make(chan int)
wg.Add(2)
go producer(ch)
go consumer(ch)
wg.Wait()
}
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/why/Desktop/go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go /Users/why/Desktop/go/why.go #gosetup
/private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go #gosetup
result: 1
result: 2
result: 3
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x1195ae8)
/usr/local/go/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0x1195ae0)
/usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
/Users/why/Desktop/go/why.go:30 +0xb1
goroutine 18 [chan send]:
main.producer(0xc000070060)
/Users/why/Desktop/go/why.go:12 +0x7c
created by main.main
/Users/why/Desktop/go/why.go:27 +0x7f
Process finished with exit code 2
可以发现错误出现在chan send
消费者阻塞:
消费4条消息,只生产3条
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func producer(ch chan<- int) {
defer wg.Done()
for i := 1; i <= 3; i++ {
ch <- i
}
}
func consumer(ch <-chan int){
defer wg.Done()
for i := 1; i <= 4; i++ {
fmt.Printf("result: %d\n", <-ch)
}
}
func main() {
ch := make(chan int)
wg.Add(2)
go producer(ch)
go consumer(ch)
wg.Wait()
}
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/why/Desktop/go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go /Users/why/Desktop/go/why.go #gosetup
/private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go #gosetup
result: 1
result: 2
result: 3
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x1195ae8)
/usr/local/go/src/runtime/sema.go:56 +0x42
sync.(*WaitGroup).Wait(0x1195ae0)
/usr/local/go/src/sync/waitgroup.go:130 +0x64
main.main()
/Users/why/Desktop/go/why.go:30 +0xb1
goroutine 7 [chan receive]:
main.consumer(0xc000070060)
/Users/why/Desktop/go/why.go:19 +0x9b
created by main.main
/Users/why/Desktop/go/why.go:28 +0xa1
Process finished with exit code 2
可以发现错误出现在chan receive
思考优化
实际应用场景,我们并不能预先知道生产者和消费者的准确数量,该如何保证不发生阻塞造成死锁呢?
Golang中提供了一个函数:close(ch),该函数是用来关闭通道的,我们在发送完需要发送的数据后,就用close将通道关闭,消息接收端接收可以借用 for range 语句进行多个元素的接收操作
举个例子帮助理解:北京的早高峰是非常吓人的,尤其是著名的天通苑,每天地铁都会进行限流,工作人员限流(用close关闭通道)后,人就不再进入地铁站(发送的协程停止发送消息),而进入地铁站的人都按照进站(消息产生)的顺序依次进行安检(通过for range依次获取消息),进入地铁站乘坐地铁(消费者处理)。
所以最终代码如下:
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func producer(ch chan<- int) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
}
close(ch)
}
func consumer(ch <-chan int){
defer wg.Done()
for res := range ch {
fmt.Println(res)
}
}
func main() {
ch := make(chan int)
wg.Add(2)
go producer(ch)
go consumer(ch)
wg.Wait()
}
GOPATH=/Users/why/Desktop/go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go /Users/why/Desktop/go/why.go #gosetup
/private/var/folders/_s/jfrm6_712w58sytpc753pmr40000gn/T/___go_build_why_go #gosetup
1
2
3
4
5
Process finished with exit code 0