别摸我
别摸我
文章目录
  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 中的最后, rangeChannels 中接收数据, 但由于前面的 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 goroutineChannels 中接收数据, 当 Channels 中的数据被取出时, 发送数据到 Channelsgoruntine 就不在阻塞了, 接着执行 wg.Done(), 当 wg 中的所有都被 Done() 了, wg.Wait() 也就不在阻塞了, 这样所有的 goroutine 都不会阻塞了, 都会正常退出。

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