我正在尝试通过同时将计算分为100组来计算阶乘的挑战,我解决了WaitGroups上的许多问题,但仍然在calculateFactorial功能上遇到了通道部分范围上的僵局。希望有人可以在这里指出问题,谢谢。
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改为缓冲通道即可解决上述代码中的问题。但是在有一个报价
oneResult
您绝不应该仅仅为了修复死锁而添加缓冲。如果您的程序陷入僵局,则从零缓冲开始并仔细考虑依赖关系,可以轻松解决问题。然后在知道不会死锁的情况下添加缓冲。
所以有人可以帮我弄清楚如何不使用缓冲通道吗?可能吗?
此外,我对导致死锁的确切原因进行了一些研究。
如果通道未缓冲,则发送方将阻塞,直到接收方收到该值为止。如果通道具有缓冲区,则发送方仅阻塞该值,直到将值复制到该缓冲区为止;否则,发送方才阻塞。如果缓冲区已满,则意味着要等到某些接收器检索到一个值。 否则说: 当频道已满时,发送方等待另一个goroutine通过接收来腾出空间 您会看到一个未缓冲的通道总是一个完整的通道:必须有另一个goroutine来处理发送方发送的内容。
如果通道未缓冲,则发送方将阻塞,直到接收方收到该值为止。如果通道具有缓冲区,则发送方仅阻塞该值,直到将值复制到该缓冲区为止;否则,发送方才阻塞。如果缓冲区已满,则意味着要等到某些接收器检索到一个值。
否则说:
当频道已满时,发送方等待另一个goroutine通过接收来腾出空间
您会看到一个未缓冲的通道总是一个完整的通道:必须有另一个goroutine来处理发送方发送的内容。
因此,在我的原始情况下,可能导致死锁的原因可能是:
通道范围未收到信号?
在单独的go例程中未接收到整个通道范围。?
在oneResult未正确关闭,所以射程超过通道不知道哪里是尽头?
对于数字3,我不知道关闭oneResult之前的范围是否有任何错误,因为此模式出现在互联网上的许多示例中。如果是3号,那么在等待组中可能有问题吗?
我收到了另一篇与我的情况非常相似的文章https://robertbasic.com/blog/buffered-vs-unbuffered- channels-in-golang/,在第二课中,他使用for { select {} }无限循环作为覆盖范围的替代方法,看来解决了他的问题。
for { select {} }
go func() { for{ select { case p := <-pch: findcp(p) } } }()
第2课-未缓冲的通道不能保留值(是的,它就以“未缓冲”的名称存在),因此,发送到该通道的任何内容都必须立即被其他一些代码接收。接收代码必须位于不同的goroutine中,因为一个goroutine不能同时做两件事:它不能发送和接收;它必须是另一个。
谢谢
死锁不在跨通道范围循环中。如果在操场上运行代码,则会在堆栈跟踪的顶部看到该错误是由引起的wg2.Wait(操场上的第88行并由堆栈跟踪指向)。同样在stacktrace中,您可以看到由于死锁而尚未完成的所有goroutine,这是因为它oneResult<-t从未完成,因此循环中启动的所有goroutine都不会完成。
wg2.Wait
oneResult<-t
所以主要问题在这里:
wg2.Wait() close(oneResult) // ... for n := range oneResult{ // ...
我想,也不希望在封闭的通道上循环。但是,即使您没有关闭通道,该循环也将永远不会开始,因为wg2.Wait()它将等待直到 完成 。
wg2.Wait()
oneResult <- t wg2.Done()
但是永远不会做,因为它依赖于已经在运行的循环。oneResult <- t除非另一侧有人从该通道接收信号,这是您的循环,否则该行将不会完成,但是,通道范围循环仍在等待wg2.Wait()完成。
oneResult <- t
因此,实质上,您在通道的发送方和接收方之间具有“循环依赖”。
要解决此问题,您需要允许循环从通道开始接收,同时仍要确保完成后通道已关闭。您可以通过将两条等待和关闭行包装到它们自己的goroutine中来进行操作。
https://play.golang.com/p/rwwCFVszZ6Q