我正在练习通过同时将计算分为 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
}
Update:
感谢 Jessé Catrinck 的回答,只需更改即可解决上述代码中的问题oneResult
到缓冲通道。然而在https://stackoverflow.com/a/15144455/921082 https://stackoverflow.com/a/15144455/921082有一个报价
绝不应该仅仅为了解决死锁而添加缓冲。如果你的
程序死锁,从零开始修复要容易得多
缓冲并思考依赖关系。然后添加缓冲
你知道这不会陷入僵局。
那么有人可以帮我弄清楚如何不使用缓冲通道吗?是否可以?
此外,我还研究了到底是什么导致了僵局。
有些引用来自https://stackoverflow.com/a/18660709/921082 https://stackoverflow.com/a/18660709/921082,
如果通道没有缓冲,则发送方会阻塞,直到接收方有
收到了值。如果通道有缓冲区,发送方会阻塞
直到该值被复制到缓冲区;如果缓冲区是
full,这意味着等待某个接收者检索到值。
另有说法:
当通道已满时,发送者等待另一个 goroutine 进行发送
一些房间通过接收
您可以将无缓冲通道视为始终满的通道:必须有另一个 goroutine 来接收发送者发送的内容。
所以在我原来的情况下,导致僵局的可能是:
频道范围内未接收到?
通道上的范围未在单独的 goroutine 上接收。 ?
the oneResult
没有正确关闭,所以通道范围不知道终点在哪里?
对于第3个,我不知道关闭它是否有什么问题oneResult
在范围结束之前,因为这种模式出现在互联网上的许多示例中。如果是3号,那么等待组中是否有问题?
我得到了另一篇文章与我的情况非常相似https://robertbasic.com/blog/buffered-vs-unbuffered-channels-in-golang/ https://robertbasic.com/blog/buffered-vs-unbuffered-channels-in-golang/,在其
吸取的第二个教训,他使用了for { select {} }
无限循环作为 range over 的替代方案,似乎解决了他的问题。
go func() {
for{
select {
case p := <-pch:
findcp(p)
}
}
}()
第 2 课 — 无缓冲通道无法保存值(是的,
它就在名称“unbuffered”中),所以无论发送到
该通道必须立即被其他代码接收。那
接收代码必须位于不同的 goroutine 中,因为一个 goroutine
不能同时做两件事:不能发送和接收;它
必须是其中之一。
Thanks