当前位置: 首页 > 面试题库 >

具有多个等待组的管道中通道范围内的死锁

周麒
2023-03-14
问题内容

我正在尝试通过同时将计算分为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改为缓冲通道即可解决上述代码中的问题。但是在有一个报价

您绝不应该仅仅为了修复死锁而添加缓冲。如果您的程序陷入僵局,则从零缓冲开始并仔细考虑依赖关系,可以轻松解决问题。然后在知道不会死锁的情况下添加缓冲。

所以有人可以帮我弄清楚如何不使用缓冲通道吗?可能吗?

此外,我对导致死锁的确切原因进行了一些研究。

如果通道未缓冲,则发送方将阻塞,直到接收方收到该值为止。如果通道具有缓冲区,则发送方仅阻塞该值,直到将值复制到该缓冲区为止;否则,发送方才阻塞。如果缓冲区已满,则意味着要等到某些接收器检索到一个值。

否则说:

  1. 当频道已满时,发送方等待另一个goroutine通过接收来腾出空间

  2. 您会看到一个未缓冲的通道总是一个完整的通道:必须有另一个goroutine来处理发送方发送的内容。

因此,在我的原始情况下,可能导致死锁的原因可能是:

  1. 通道范围未收到信号?

  2. 在单独的go例程中未接收到整个通道范围。?

  3. oneResult未正确关闭,所以射程超过通道不知道哪里是尽头?

对于数字3,我不知道关闭oneResult之前的范围是否有任何错误,因为此模式出现在互联网上的许多示例中。如果是3号,那么在等待组中可能有问题吗?

我收到了另一篇与我的情况非常相似的文章https://robertbasic.com/blog/buffered-vs-unbuffered-
channels-in-golang/
,在第二课中,他使用for { select {} }无限循环作为覆盖范围的替代html" target="_blank">方法,看来解决了他的问题。

 go func() {
        for{
            select {
            case p := <-pch:
                findcp(p)
            }
        }
    }()

第2课-未缓冲的通道不能保留值(是的,它就以“未缓冲”的名称存在),因此,发送到该通道的任何内容都必须立即被其他一些代码接收。接收代码必须位于不同的goroutine中,因为一个goroutine不能同时做两件事:它不能发送和接收;它必须是另一个。

谢谢


问题答案:

死锁不在跨通道范围循环中。如果在操场上运行代码,则会在堆栈跟踪的顶部看到该错误是由引起的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中来进行操作。

https://play.golang.com/p/rwwCFVszZ6Q



 类似资料:
  • 我对netty很陌生,我想创建一个TCP服务器,当连接被实例化时,它会进行自定义应用层握手。握手后,我想将消息(ByteBuf)传递给队列,以便它们可以由其他线程处理。 我的问题是,通道管道中是否可以有多个ChannelInboundHandlerAdapter?一个用于应用层握手协议,另一个用于将消息传递到队列。此外,我想知道消息是如何通过管道的。如果一个处理程序(或解码器/编码器)接收到消息,

  • 问题内容: 在包中的某个动作内,是否可以在range动作之前访问管道值,或者是否可以将父/全局管道作为参数传递给Execute? 工作示例显示了我尝试执行的操作: play.golang.org 问题答案: 使用$变量(推荐) 从软件包文本/模板文档中: 开始执行时,将$设置为传递给Execute的数据参数,即dot的起始值。 正如@Sandy所指出的,因此可以使用来访问外部作用域中的Path 。

  • 问题内容: 在本文中了解了我最初的问题的(正确)解决方案之后,了解了golang频道:死锁,我想出了一个略有不同的解决方案(在我看来,这更好看: 它确实可以工作,但是不幸的是,它在比赛中运行,它显示了2个比赛条件: 您能帮我了解比赛情况吗? 问题答案: 您正在试图从读中未同步到够程变异它的写入。您可以添加锁以同步写入和读取。 例如 请注意,您也可以使用。 您可以做的另一件事是等待goroutine

  • 问题内容: 我正在尝试使用Ruffus管道中的多个Sailq文件作为参数的Sailfish。我使用python中的子流程模块执行Sailfish,但即使设置,在子流程调用中也不起作用。 这是我要使用python执行的命令: 或(最好): 概括: 我将如何在python中执行此操作?子过程正确吗? 问题答案: 模拟bash进程替换: 在Python中,您可以使用命名管道: 在哪里: 实现bash进程

  • 我创建了一个日历应用程序,可以添加注释。为了实现添加注释的功能,我创建了一些父组件,它有自己的状态,然后传递给子组件。子组件应该在构造函数中接受来自执行的。但是,由于setState函数异步ChildComponent没有时间等待父母组件的道具。 如何设置ChildComponent的初始状态,等待父母组件的道具(换句话说,同步)? 父组件: ChildComponent:

  • 我正在努力为我的詹金斯管道获得正确的配置。 它工作,但我不知道如何分离测试 要求: < li >带单独测试的Jenkins管道 当前设置: Jenkinsfile: Dockerfile: 这很有效,但正如您所看到的“构建 我想要 2 个独立的 jenkins 阶段(测试、构建) 我已经尝试使用Jenkins代理docker并在Jenkins文件中定义测试阶段的映像,但我不知道如何在那里添加缺少的