别摸我
别摸我
文章目录
  1. 解决方案
    1. 1、使用非阻塞(带缓存)的 Channels
    2. 2. 不阻塞 main goroutine

go 中的 channel 和 goroutine 使用时发生的 deadlock 问题

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// test.go
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
array := []int{1, 2, 3}
nums := make(chan int)

for _, a := range array {
wg.Add(1)
go func(a int){
defer wg.Done()
nums <- a
}(a)
}

wg.Wait()
close(nums)

for num := range nums {
fmt.Println(num)
}
}

执行:

1
2
3
4
5
6
$ go run test.go
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0xc42008601c)
...

发生了死锁, 所有的 goroutines 都被阻塞了。

在11行, 我们初始化了一个不带缓存的 Channels , 在创建的子 goroutine 中, 我们向这个 Channels 执行了一个发送的操作。这个发送操作会导致发送者的 goroutine 阻塞, 直到另一个 goroutine 在相同的 Channels 中执行接收操作。

在 main goroutine 中的最后, range 从 Channels 中接收数据, 但由于前面的 goroutine 都被阻塞了, 所以 goroutine 中的计数器 wg 并没有执行 Done() 操作, 所以在前面的 wg.Wait() 被阻塞了, 导致 main goroutine 也被阻塞了, 也就导致了 deadlock 的发生。

解决方案

1、使用非阻塞(带缓存)的 Channels
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
array := []int{1, 2, 3}
nums := make(chan int, len(array))

for _, a := range array {
wg.Add(1)
go func(a int){
defer wg.Done()
nums <- a
}(a)
}

wg.Wait()
close(nums)

for num := range nums {
fmt.Println(num)
}
}

在通过 make 创建 Channels 时指定第二个参数时创建的就是带缓存的 Channels , 第二个参数指定了该 Channels 的容量。可以使用 len() 和 cap() 获取该带缓存的 Channels 的有效元素和容量。

带缓存的 Channels 使得向 Channels 执行发送操作不再阻塞, 但当超过缓存容量时, 会引发 panic, 所以缓存的容量大小要设置得当。

2. 不阻塞 main goroutine
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
array := []int{1, 2, 3}
nums := make(chan int)

for _, a := range array {
wg.Add(1)
go func(a int){
defer wg.Done()
nums <- a
}(a)
}
go func() {
wg.Wait()
close(nums)
}

for num := range nums {
fmt.Println(num)
}
}

这里把 wg.Wait() 操作放到了子 goroutine 中, main goroutine 从Channels 中接收数据, 当 Channels 中的数据被取出时, 发送数据到 Channels 的 goruntine 就不在阻塞了, 接着执行 wg.Done(), 当 wg 中的所有都被 Done() 了, wg.Wait() 也就不在阻塞了, 这样所有的 goroutine 都不会阻塞了, 都会正常退出。

支持一下
扫一扫,支持heaven
  • 微信扫一扫
  • 支付宝扫一扫