关于Golang使用channel的死锁问题

发现问题

小白一枚,最近在入门学习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

思考优化

实际应用场景,我们并不能预先知道生产者和消费者的准确数量,该如何保证不发生阻塞造成死锁呢?

扫描二维码关注公众号,回复: 8608021 查看本文章

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
发布了200 篇原创文章 · 获赞 26 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/why444216978/article/details/103655715