为什么在Golang/WaitGroup中会出现死锁?

huangapple go评论103阅读模式
英文:

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 &quot;fmt&quot;
import &quot;sync&quot;

func main() {
	ch := make(chan []int, 4)
	var m []int

	var wg sync.WaitGroup
	for i := 0; i &lt; 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ch &lt;- m
			return
		}()
	}
	wg.Wait()

	for c := range ch {
		fmt.Printf(&quot;c is %v&quot;, 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)

Demonstration

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 &lt; 5; i++ {
		wg.Add(1)
		go func() {
			ch &lt;- m
			return
		}()
	}
	go func() {
		for c := range ch {
			fmt.Printf(&quot;c is %v\n&quot;, c)
			wg.Done()
		}
	}()
	wg.Wait()
}

Demonstration

答案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 &lt; 5; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			ch &lt;- m
			return
		}()
	}
	go func() {
		wg.Wait()
		close(ch)
	}()
	for c := range ch {
		fmt.Printf(&quot;c is %v&quot;, c)
	}
}

form https://go.dev/blog/pipelines Fan-out, fan-in 's func merge()

func merge(cs ...&lt;-chan int) &lt;-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 &lt;-chan int) {
        for n := range c {
            out &lt;- 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 &lt; 5; i++ {
		go func() {
			ch &lt;- m
			return
		}()
	}
	for c := range ch {
		fmt.Printf(&quot;c is %v \n&quot;, c)
	}
}

huangapple
  • 本文由 发表于 2014年7月7日 19:20:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/24609395.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定