英文:
why is this a deadlock in golang / waitgroup?
问题
我不确定我漏掉了什么,但我得到了一个死锁错误。我使用了一个缓冲通道,在所有的Go协程完成后对其进行迭代。该通道的容量为4,我运行了4个Go协程,所以我期望一旦达到最大容量,它会自动"关闭"。
package main
import "fmt"
import "sync"
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ch <- m
return
}()
}
wg.Wait()
for c := range ch {
fmt.Printf("c is %v", c)
}
}
英文:
I'm not sure what I'm missing but I get a deadlock error. I'm using a buffered channel that I range over after all go routines complete. The channel has the capacity of 4 and I'm running 4 go routines so I'm expecting it to be "closed" automatically once it reaches the max capacity.
package main
import "fmt"
import "sync"
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ch <- m
return
}()
}
wg.Wait()
for c := range ch {
fmt.Printf("c is %v", c)
}
}
答案1
得分: 10
你有两个问题:
- 你的通道容量太小,没有足够的空间容纳所有的goroutine:当通道已满时,剩余的goroutine必须等待一个空槽。
range ch
仍在等待通道中的元素,而没有剩余的goroutine可以写入它。
解决方案1:
将通道的容量调大,并关闭它以使range
停止等待:
ch := make(chan []int, 5)
...
wg.Wait()
close(ch)
这个方法可以解决问题,但它基本上破坏了通道的目的,因为在所有任务完成之前你不会开始打印。
解决方案2:
这个解决方案可以实现真正的流水线(即较小的通道缓冲区),在打印时执行Done()
:
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
ch <- m
return
}()
}
go func() {
for c := range ch {
fmt.Printf("c is %v\n", c)
wg.Done()
}
}()
wg.Wait()
}
英文:
You have two problems :
- there's not enough place for all the goroutines as your channel is too small : when your channel is full, the remaining goroutines must wait for a slot to be freed
range ch
is still waiting for elements to come in the channel and there's no goroutine left to write on it.
Solution 1 :
Make the channel big enough and, close it so that range
stops waiting :
ch := make(chan []int, 5)
...
wg.Wait()
close(ch)
This works but this mostly defeats the purpose of channels here as you don't start printing before all tasks are done.
Solution 2 :
This solution, which would allow a real pipelining (that is a smaller channel buffer), would be to do the Done()
when printing :
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
ch <- m
return
}()
}
go func() {
for c := range ch {
fmt.Printf("c is %v\n", c)
wg.Done()
}
}()
wg.Wait()
}
答案2
得分: 9
我正在运行4个Go协程。
不,你正在运行5个协程-这就是死锁的原因,因为通道缓冲区只能容纳4个消息。
然而,遍历通道也会导致程序死锁。由于通道没有关闭,在读取了5个值后,它将永远阻塞。
所以,在range
循环之前,你需要使用以下代码:
ch := make(chan []int, 5)
和
close(ch)
英文:
> I'm running 4 go routines
No, you're running 5 - thus the deadlock, as the channel buffers only 4 messages.
However, iterating over the channel will later on deadlock the program too. Since the channel isn't closed, it'll block forever once it read your 5 values.
So,
ch := make(chan []int, 5)
and
close(ch)
before the range
loop.
答案3
得分: 1
@Denys Séguret是正确的,但是下面是另一个更容易修复的解决方案:
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ch <- m
return
}()
}
go func() {
wg.Wait()
close(ch)
}()
for c := range ch {
fmt.Printf("c is %v", c)
}
}
来源:https://go.dev/blog/pipelines 中的Fan-out, fan-in的函数merge()
func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
// 为每个输入通道启动一个输出goroutine。output从c复制值到out,直到c关闭,然后调用wg.Done。
output := func(c <-chan int) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// 启动一个goroutine,在所有输出goroutine完成后关闭out。这必须在wg.Add调用之后启动。
go func() {
wg.Wait()
close(out)
}()
return out
}
英文:
@Denys Séguret is right
but follow is anohter Solution easier to fix it:
func main() {
ch := make(chan []int, 4)
var m []int
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
ch <- m
return
}()
}
go func() {
wg.Wait()
close(ch)
}()
for c := range ch {
fmt.Printf("c is %v", c)
}
}
form https://go.dev/blog/pipelines Fan-out, fan-in 's func merge()
func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
// Start an output goroutine for each input channel in cs. output
// copies values from c to out until c is closed, then calls wg.Done.
output := func(c <-chan int) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// Start a goroutine to close out once all the output goroutines are
// done. This must start after the wg.Add call.
go func() {
wg.Wait()
close(out)
}()
return out
}
答案4
得分: 0
你可以尝试运行这段代码,它没有使用wg,但仍然会发生死锁。为什么呢?这是因为主goroutine被ch阻塞,所以程序被挂起并超时,最终导致死锁错误。根本原因是ch阻塞了主goroutine。
func main() {
ch := make(chan []int, 4) // 不管是哪个int数字
var m []int
for i := 0; i < 5; i++ {
go func() {
ch <- m
return
}()
}
for c := range ch {
fmt.Printf("c is %v \n", c)
}
}
英文:
you can try run this code that don`t have wg ,and still deadlock ;
why?
Just because the main goroutine is blocked by the ch , so the program is suspended and timeout , and you get an deadlock error.
The essential reason is that ch is blocking the main goroutine.
func main() {
ch := make(chan []int, 4) // No matter which int number
var m []int
for i := 0; i < 5; i++ {
go func() {
ch <- m
return
}()
}
for c := range ch {
fmt.Printf("c is %v \n", c)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论