Попробуйте что-то вроде этого (пояснение ниже):
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()
}
В произвольном порядке:
- Вместо использования
io.Pipe()
в качестве Stdout
команды, простоспросите у команды это StdoutPipe()
.cmd.Wait()
гарантирует, что он для вас закрыт. - Установите
cmd.Stderr
на os.Stderr
, чтобы вы могли видеть ошибки, генерируемые вашей программой Python. - Я заметил, что эта программа зависала каждый раз, когда программа Python писала со стандартной ошибкой.Теперь это не так:
- Не делайте
WaitGroup
глобальной переменной;передать ссылку на него в программу. - Вместо того, чтобы
log.Fatal()
вводить внутри программы, создайте канал ошибок, чтобы сообщать об ошибках обратно в main()
. - Вместо того, чтобы печатать результаты внутриgoroutine, создайте канал результатов для передачи результатов обратно на
main()
. - Убедитесь, что каналы закрыты для предотвращения блокировки / утечки goroutine.
- Разделите goroutine на правильную функцию для создания кодалегче читать и следовать.
- В этом примере мы можем создать
multipart.Reader()
внутри нашей программы, поскольку это единственная часть нашего кода, которая его использует. - Обратите внимание, что яиспользуя
Wrap()
из пакета errors
, чтобы добавить контекст к сообщениям об ошибках.Это, конечно, не относится к вашему вопросу, но это хорошая привычка.
Часть for { select { ... } }
может сбивать с толку. Эта - одна статья, которую я нашел, представляя концепцию.По сути, select
позволяет нам читать с любого из этих двух каналов (resc
и errc
) в настоящее время для чтения, а затем устанавливать каждый на nil
, когда канал закрыт.Когда оба канала nil
, цикл завершается.Это позволяет нам обрабатывать «либо результат, либо ошибку» по мере их поступления.
Edit: Как сказал johandalabacka на форуме Golang , он выглядит как основнойПроблема заключалась в том, что Python в Windows добавлял к выводу дополнительный \r
, и проблема в том, что ваша программа на Python должна пропустить \r
в выходной строке или sys.stdout.write()
вместо print()
ing.Вывод также может быть очищен на стороне Golang, но, помимо невозможности правильного анализа без модификации стороны Python, этот ответ все же улучшит механику параллелизма вашей программы.