您的位置:首页 > 编程语言 > Go语言

go - 关于使用 channel 时遇到的死锁问题

2017-10-19 11:47 274 查看

1,发生死锁的代码

func deadlockTest() {

ch := make(chan int)
results := make(chan int)

for i := 0; i < 2; i++ {
go func() {
// 把从channel里取得的数据,再传回去
x := <-ch
results <- x

}()
}

// 向输入数据里传两个数据
ch <- 1
ch <- 2

for re := range results {
fmt.Printf("re:%v\n", re)
}

}


结果:

re:1
re:2
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.deadlockTest()
/Users/iss/goproj/go_bible/src/main.go:45 +0x174
main.main()
/Users/iss/goproj/go_bible/src/main.go:14 +0x20

Process finished with exit code 0


2,发生死锁的原因

主程序中的
for re := range results
代码,在处理完
1
2
两条数据后,还一直在等待 results channel 的内容,无法结束。这样的话,Go就会判定产生了死锁

for re := range results {
fmt.Printf("re:%v\n", re)
}


3,如果解决这个死锁问题

方案1:

代码

func deadlockTestOKVer1() {

ch := make(chan int)
results := make(chan int)

for i := 0; i < 2; i++ {
go func() {
// 把从channel里取得的数据,再传回去
x := <-ch
results <- x

}()
}

// 向输入数据里传两个数据
ch <- 1
ch <- 2

go func() {
for re := range results { fmt.Printf("re:%v\n", re) }
}()

// 等待3秒后,才结束主程序。
// 如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。
time.Sleep(3 * time.Second)

}


解析:

1,这个方法是把 results channel 的接收处理,放到一个goroutine里去做。这样的话,主程序就不会卡住不动,即产生死锁了。

2,然后,在最后一行加了一个等待(time.Sleep(3 * time.Second))。如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。(和Sleep相比,还有更好的方法,比如使用WaitGroup)

(fatal error: all goroutines are asleep - deadlock!)这种抛出 error 的死锁,是只有主程序卡住不动才会生产的吗?如果是的话,那不让主程序卡住不动,就不会发生这种事情了。所以以下的各种解决方案,都是围绕如何“不让主程序卡住”来做的。

方法2:

代码

func deadlockTestOKVer2() {

var wg sync.WaitGroup
ch := make(chan int)
results := make(chan int)

for i := 0; i < 2; i++ {
wg.Add(1)
go func() {
// 把从channel里取得的数据,再传回去
x := <-ch
results <- x

defer wg.Done()

}()
}

// 在(for i := 0; i < 2; i++)循环里的 go func 都执行完后,
// 关闭 result channel,这样主程序里的 for reuslts 循环 就可以正常结束了。
go func() {
wg.Wait()
close(results)
}()

// 向输入数据里传两个数据
ch <- 1
ch <- 2

for re := range results { fmt.Printf("re:%v\n", re) }

// 等待3秒后,才结束主程序。
// 如果不加等待的话,上面的 go func 可能还没有执行,程序就结束了,go func 跟着结束了。
time.Sleep(3 * time.Second)

}


解析

最上面死锁的原因是,
for result
循环一直在等待输入,但实际上输入只有2个,处理完后就没有了。为了解决这个问题,把 results channel 关闭,
for result
循环就可以正常退出了。

具体细节是,在所有的 results channel 的输入处理之前,
wg.Wait()
这个goroutine会处于等待状态。当处理完后(wg.Done),
wg.Wait()
就会放开执行,执行后面的
close(results)


注意:要把
wg.Add(1)
放到
go func
外面。如果放到里面的话,(for i := 0; i < 2; i++)里的
go func
有可能会在
wg.wait
go func
之后执行,这样
close(results)
就会先执行。

4,什么样的情况会死锁?

总结一下,channel 上如果发生了
流入和流出不配对
,就可能会发生死锁。

例子:

(1)无缓冲区死锁
func deadlockTest1() {
ch := make(chan int)
ch <- 0
}

(2)有缓冲区死锁
func deadlockTest1() {
ch := make(chan int, 1)
ch <- 0
<- ch
<- ch
}


如何不发生死锁

1,明确保证流入和流出成对出现

2,当无法保证流入和流出成对出现时(例如上面的 for range),在流入完成后,关闭 channel。(上面的方法2)

3,利用缓冲。明确保证流入成对出现的话,可以使用缓冲来解决
流入
的阻塞。

func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3

for v := range ch {
fmt.Println(v)
}
}


参考

Go语言并发与并行学习笔记(一)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  go 并发 死锁