У меня есть следующая программа:
package main
import "bytes"
import "io"
import "log"
import "os"
import "os/exec"
import "time"
func main() {
runCatFromStdinWorks(populateStdin("aaa\n"))
runCatFromStdinWorks(populateStdin("bbb\n"))
}
func populateStdin(str string) func(io.WriteCloser) {
return func(stdin io.WriteCloser) {
defer stdin.Close()
io.Copy(stdin, bytes.NewBufferString(str))
}
}
func runCatFromStdinWorks(populate_stdin_func func(io.WriteCloser)) {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Panic(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Panic(err)
}
err = cmd.Start()
if err != nil {
log.Panic(err)
}
go populate_stdin_func(stdin)
go func() {
// Removing the following lines allow some output
// to be fetched from cat stdout sometimes
time.Sleep(5 * time.Second)
io.Copy(os.Stdout, stdout)
}()
err = cmd.Wait()
if err != nil {
log.Panic(err)
}
}
При запуске в цикле я не получаю никаких результатов, например:
$ while true; do go run cat_thingy.go; echo ; done
^C
Этот результат возникает после установки golang-go на Ubuntu 12.04 с apt в виртуальной машине (go version go1). Я не смог воспроизвести установку на Macbook Air (go version go1.0.3). Кажется, это какое-то состояние гонки. На самом деле, если я поставлю сон (1 * time.Second), я никогда не вижу проблему за счет случайного сна в моем коде.
Есть ли что-то, что я делаю неправильно в коде, или это ошибка? Если это ошибка, исправлена ли она?
ОБНОВЛЕНИЕ: Возможная подсказка
Я обнаружил, что Command.Wait закроет каналы для связи с/подкодом cat, даже если у них все еще есть непрочитанные данные. Я не совсем уверен в правильном способе справиться с этим. Думаю, я мог бы создать канал для уведомления, когда запись в stdin будет завершена, но мне все равно нужно будет знать, закончился ли процесс cat, чтобы убедиться, что в его трубе stdout ничего не будет записано. Я знаю, что могу использовать cmd.Process.Wait, чтобы определить, когда закончится процесс, но безопасно ли тогда вызвать cmd.Wait?
ОБНОВЛЕНИЕ: Ускорение
Здесь новый фрагмент кода. Я считаю, что это работает до написания stdin и чтения из stdout. Я думаю, что я могу заставить его правильно передавать данные (вместо того, чтобы буферизировать все это), если я заменил io.Copy на stdout-обработку goroutine без потокового потока.
package main
import "bytes"
import "fmt"
import "io"
import "log"
import "os/exec"
import "runtime"
const inputBufferBlockLength = 3*64*(2<<10) // enough to be bigger than 2x the pipe buffer of 64KiB
const numInputBlocks = 6
func main() {
runtime.GOMAXPROCS(5)
runCatFromStdin(populateStdin(numInputBlocks))
}
func populateStdin(numInputBlocks int) func(io.WriteCloser, chan bool) {
return func(stdin io.WriteCloser) {
defer stdin.Close()
repeatedByteBases := []string{"a", "b", "c", "d", "e", "f"}
for i := 0; i < numInputBlocks; i++ {
repeatedBytes := bytes.NewBufferString(repeatedByteBases[i]).Bytes()
fmt.Printf("%s\n", repeatedBytes)
io.Copy(stdin, bytes.NewReader(bytes.Repeat(repeatedBytes, inputBufferBlockLength)))
}
}
}
func runCatFromStdin(populate_stdin_func func(io.WriteCloser)) {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Panic(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Panic(err)
}
err = cmd.Start()
if err != nil {
log.Panic(err)
}
go populate_stdin_func(stdin)
output_done_channel := make(chan bool)
go func() {
out_bytes := new(bytes.Buffer)
io.Copy(out_bytes, stdout)
fmt.Printf("%s\n", out_bytes)
fmt.Println(out_bytes.Len())
fmt.Println(inputBufferBlockLength*numInputBlocks)
output_done_channel <- true
}()
<-output_done_channel
err = cmd.Wait()
if err != nil {
log.Panic(err)
}
}