为了账号安全,请及时绑定邮箱和手机立即绑定

从外部命令读取错误:致命错误所有 goroutines 都睡着了 - 死锁

从外部命令读取错误:致命错误所有 goroutines 都睡着了 - 死锁

Go
慕码人8056858 2023-04-17 16:37:09
我想用 Python 将 mime/multipart 消息写入标准输出,并使用mime/multipart包在 Golang 中读取该消息。这只是一个学习练习。我试着模拟这个例子。output.py#!/usr/bin/env python2.7import syss = "--foo\r\nFoo: one\r\n\r\nA section\r\n" +"--foo\r\nFoo: two\r\n\r\nAnd another\r\n" +"--foo--\r\n"print s 主程序package mainimport (    "io"    "os/exec"    "mime/multipart"    "log"    "io/ioutil"    "fmt"    "sync")var wg sync.WaitGroupfunc main() {    pr,pw := io.Pipe()    defer pw.Close()    cmd := exec.Command("python","output.py")    cmd.Stdout = pw    mr := multipart.NewReader(pr,"foo")    wg.Add(1)    go func() {        defer wg.Done()        for {            p, err := mr.NextPart()            if err == io.EOF {                fmt.Println("EOF")                return            }            if err != nil {                log.Fatal(err)            }            slurp, err := ioutil.ReadAll(p)            if err != nil {                log.Fatal(err)            }            fmt.Printf("Part : %q\n", slurp)            return        }    }()    if err := cmd.Start(); err != nil {        log.Fatal(err)    }    cmd.Wait()    wg.Wait()}输出go run main.go:fatal error: all goroutines are asleep - deadlock!StackOverflow 上有关此主题的其他答案与未关闭的频道有关,但我什至没有使用频道。我知道某个地方有无限循环或类似的东西,但我没有看到。
查看完整描述

1 回答

?
慕沐林林

TA贡献2016条经验 获得超9个赞

尝试这样的事情(下面的解释):


package main


import (

    "fmt"

    "io"

    "io/ioutil"

    "log"

    "mime/multipart"

    "os"

    "os/exec"

    "sync"


    "github.com/pkg/errors"

)


func readCommand(cmdStdout io.ReadCloser, wg *sync.WaitGroup, resc chan<- []byte, errc chan<- error) {

    defer wg.Done()

    defer close(errc)

    defer close(resc)


    mr := multipart.NewReader(cmdStdout, "foo")


    for {

        part, err := mr.NextPart()

        if err != nil {

            if err == io.EOF {

                fmt.Println("EOF")

            } else {

                errc <- errors.Wrap(err, "failed to get next part")

            }


            return

        }


        slurp, err := ioutil.ReadAll(part)

        if err != nil {

            errc <- errors.Wrap(err, "failed to read part")

            return

        }


        resc <- slurp

    }

}


func main() {

    cmd := exec.Command("python", "output.py")

    cmd.Stderr = os.Stderr

    pr, err := cmd.StdoutPipe()

    if err != nil {

        log.Fatal(err)

    }


    var wg sync.WaitGroup

    wg.Add(1)


    resc := make(chan []byte)

    errc := make(chan error)

    go readCommand(pr, &wg, resc, errc)


    if err := cmd.Start(); err != nil {

        log.Fatal(err)

    }


    for {

        select {

        case err, ok := <-errc:

            if !ok {

                errc = nil

                break

            }


            if err != nil {

                log.Fatal(errors.Wrap(err, "error from goroutine"))

            }


        case res, ok := <-resc:

            if !ok {

                resc = nil

                break

            }


            fmt.Printf("Part from goroutine: %q\n", res)

        }


        if errc == nil && resc == nil {

            break

        }

    }


    cmd.Wait()

    wg.Wait()

}

排名不分先后:

  • 与其使用 anio.Pipe()作为命令的Stdout,不如向命令询问它的StdoutPipe()cmd.Wait()将确保它为您关闭。

  • 设置cmd.Stderros.Stderr以便您可以查看 Python 程序生成的错误。

    • 我注意到只要 Python 程序写入标准错误,这个程序就会挂起。现在它没有:)

  • 不要将其设为WaitGroup全局变量;将对它的引用传递给 goroutine。

  • 与其log.Fatal()在 goroutine 内部执行 ing,不如创建一个错误通道来将错误传回给main().

  • 与其在 goroutine 中打印结果,不如创建一个结果通道将结果传回给main().

  • 确保通道关闭以防止阻塞/goroutine 泄漏。

  • 将 goroutine 分离到一个适当的函数中,使代码更易于阅读和遵循。

  • 在这个例子中,我们可以在我们的 goroutine 内部创建multipart.Reader(),因为这是我们代码中唯一使用它的部分。

  • 请注意,我使用Wrap()fromerrors包来为错误消息添加上下文。当然,这与您的问题无关,但这是一个好习惯。

for { select { ... } }部分可能令人困惑。


查看完整回答
反对 回复 2023-04-17
  • 1 回答
  • 0 关注
  • 124 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信