我正在通过同时将计算分为100组来练习计算阶乘的挑战,我解决了WaitGroups上的许多问题,但是仍然在calculateFactorial
函数中,通道范围上出现了僵局。
希望有人可以在这里指出问题,谢谢。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
in := make (chan int)
out := make (chan float64)
out = calculateFactorial(genConcurrentGroup(in, &wg), &wg)
go func() {
in <- 10
close(in)
}()
fmt.Println(<-out)
wg.Wait()
}
//split input number into groups
//the result should be a map of [start number, number in group]
//this is not heavy task so run in one go routine
func genConcurrentGroup(c chan int, wg *sync.WaitGroup) chan map[int]int{
out := make(chan map[int]int)
go func() {
//100 groups
total:= <- c
wg.Done()
//element number in group
elemNumber := total / 100
extra := total % 100
result := make(map[int]int)
if elemNumber>0{
//certain 100 groups
for i:=1 ;i<=99;i++{
result[(i-1) * elemNumber + 1] = elemNumber
}
result[100] = extra + elemNumber
}else{
//less than 100
for i:=1;i<=total;i++{
result[i] = 1
}
}
out <- result
close(out)
}()
return out
}
//takes in all numbers to calculate multiply result
//this could be heavy so can do it 100 groups together
func calculateFactorial(nums chan map[int]int, wg *sync.WaitGroup) chan float64{
out := make(chan float64)
go func() {
total:= <- nums
wg.Done()
fmt.Println(total)
oneResult := make(chan float64)
var wg2 sync.WaitGroup
wg2.Add(len(total))
for k,v := range total{
fmt.Printf("%d %d \n",k,v)
go func(k int, v int) {
t := 1.0
for i:=0;i<v;i++{
t = t * (float64(k) + float64(i))
}
fmt.Println(t)
oneResult <- t
wg2.Done()
}(k,v)
}
wg2.Wait()
close(oneResult)
result := 1.0
for n := range oneResult{ //DEADLOCK HERE! Why?
result *= n
}
fmt.Printf("Result: %f\n",result)
out <- result
}()
return out
}
更新:
感谢JesséCatrinck的回答,只需将oneResult
更改为缓冲通道即可解决上述代码中的问题。但是在https://stackoverflow.com/a/15144455/921082中有一个引号
您永远不应仅为了修复死锁而添加缓冲。如果你的 程序死锁,从零开始更容易修复 缓冲并仔细考虑依赖项。然后在 你知道它不会死锁。
那么有人可以帮助我弄清楚如何不使用缓冲通道吗?有可能吗?
此外,我对导致死锁的确切原因进行了一些研究。
一些引号,例如https://stackoverflow.com/a/18660709/921082,
如果通道未缓冲,则发送方阻塞,直到接收方收到 收到了价值。如果通道有缓冲区,则发送方阻止 仅在将值复制到缓冲区之前;如果缓冲区是 已满,这意味着要等到某些接收者检索到一个值。
否则:
- 获得一些空间
当频道已满时,发送方等待另一个goroutine生成 通过接收
您会看到一个未缓冲的通道总是一个完整的通道:必须有另一个goroutine来处理发送方发送的内容。
所以在我的原始情况下,可能导致死锁的原因可能是:
通道范围未收到信号?
单独的go例程未接收到通道范围。 ?
oneResult
未正确关闭,因此跨通道范围不知道终点在哪里?
对于数字3,我不知道在范围结束之前关闭oneResult
是否有什么问题,因为这种模式出现在互联网上的许多示例中。如果是3号,那么在等待组中有什么问题吗?
另一篇文章与我的情况https://robertbasic.com/blog/buffered-vs-unbuffered-channels-in-golang/非常相似,
第二课,他使用for { select {} }
无限循环作为范围的替代方法,似乎解决了他的问题。
go func() {
for{
select {
case p := <-pch:
findcp(p)
}
}
}()
第2课-无缓冲的频道无法保留值(是的, 它以“ unbuffered”的名称存在),因此无论发送到 该频道,则必须立即由其他一些代码接收。那 接收代码必须位于不同的goroutine中,因为一个goroutine 不能同时做两件事:无法发送和接收;它 必须是另一个。
谢谢
答案 0 :(得分:1)
您需要为变量functionOne has been called
with argument: hello
添加一个缓冲区
说明:https://www.rapidloop.com/blog/golang-channels-tips-tricks.html
答案 1 :(得分:1)
死锁不在通道范围内的循环上。如果您在playground上运行代码,则会在堆栈跟踪的顶部看到该错误是由wg2.Wait
引起的(操场上的第88行,由堆栈跟踪指向)。同样在stacktrace中,您可以看到由于死锁而尚未完成的所有goroutine,这是因为oneResult<-t
从未完成,因此循环中启动的所有goroutine都没有完成。
所以主要问题在这里:
wg2.Wait()
close(oneResult)
// ...
for n := range oneResult{
// ...
我想,也不希望在封闭的通道上循环。但是,即使您没有关闭频道,该循环也永远不会开始,因为wg2.Wait()
将等到其完成。
oneResult <- t
wg2.Done()
但是永远不会做,因为它依赖于已经在运行的循环。除非另一边有人从该通道接收到您的循环,否则oneResult <- t
行将不会完成,但是,通道范围循环仍在等待wg2.Wait()
完成。
因此,基本上,您在频道的发送方和接收方之间具有“循环依赖”。
要解决此问题,您需要允许循环开始从通道接收,同时仍确保完成后通道已关闭。您可以通过将两条等待关闭行包装到它们自己的goroutine中来做事。