如何从一个io.Reader获得多个消费者?

时间:2014-07-10 13:02:48

标签: stream go

我正在开发一个小脚本,它使用bufio.Scannerhttp.Request以及例程来并行计算单词和行。

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/http"
    "time"
)

func main() {
    err := request("http://www.google.com")

    if err != nil {
        log.Fatal(err)
    }

    // just keep main alive with sleep for now
    time.Sleep(2 * time.Second)
}

func request(url string) error {
    res, err := http.Get(url)

    if err != nil {
        return err
    }

    go scanLineWise(res.Body)
    go scanWordWise(res.Body)

    return err
}

func scanLineWise(r io.Reader) {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanLines)

    i := 0

    for s.Scan() {
        i++
    }

    fmt.Printf("Counted %d lines.\n", i)
}

func scanWordWise(r io.Reader) {
    s := bufio.NewScanner(r)
    s.Split(bufio.ScanLines)

    i := 0

    for s.Scan() {
        i++
    }

    fmt.Printf("Counted %d words.\n", i)
}

Source

来自流scanLineWise的或多或少的预期将计算一个数字,而scalWordWise将计为零。这是因为scanLineWise已经从req.Body读取了所有内容。

我想知道:如何优雅地解决这个问题?

我的第一个想法是构建一个实现io.Readerio.Writer的结构。我们可以使用io.Copyreq.Body读取并将其写入writer。当扫描仪从该编写器读取时,编写器将复制数据而不是读取数据。不幸的是,这只会随着时间的推移收集内存并打破整个流的想法......

2 个答案:

答案 0 :(得分:15)

选项非常简单 - 您要么维护"流"数据,或缓冲身体。

如果你确实需要多次阅读身体,那么你需要在某处缓冲它。没有办法解决这个问题。

您可以通过多种方式传输数据,例如将行计数器输出行放入字计数器(最好通过通道)。您还可以使用io.TeeReaderio.Pipe构建管道,并为每个函数提供唯一的读取器。

...
pipeReader, pipeWriter := io.Pipe()
bodyReader := io.TeeReader(res.Body, pipeWriter)
go scanLineWise(bodyReader)
go scanWordWise(pipeReader)
...

但是,对于更多的消费者而言,这可能会变得笨拙,因此您可以使用io.MultiWriter多路复用到更多io.Readers

...
pipeOneR, pipeOneW := io.Pipe()
pipeTwoR, pipeTwoW := io.Pipe()
pipeThreeR, pipeThreeW := io.Pipe()

go scanLineWise(pipeOneR)
go scanWordWise(pipeTwoR)
go scanSomething(pipeThreeR)

// of course, this should probably have some error handling
io.Copy(io.MultiWriter(pipeOneW, pipeTwoW, pipeThreeW), res.Body)
...

答案 1 :(得分:5)

您可以使用频道,在scanLineWise中进行实际阅读,然后将行传到scanWordWiseexample

func countLines(r io.Reader) (ch chan string) {
    ch = make(chan string)
    go func() {
        s := bufio.NewScanner(r)
        s.Split(bufio.ScanLines)

        cnt := 0

        for s.Scan() {
            ch <- s.Text()
            cnt++
        }
        close(ch)
        fmt.Printf("Counted %d lines.\n", cnt)
    }()

    return
}

func countWords(ch <-chan string) {
    cnt := 0
    for line := range ch {
        s := bufio.NewScanner(strings.NewReader(line))
        s.Split(bufio.ScanWords)
        for s.Scan() {
            cnt++
        }
    }
    fmt.Printf("Counted %d words.\n", cnt)
}

func main() {
    r := strings.NewReader(body)
    ch := countLines(r)
    go countWords(ch)
    time.Sleep(1 * time.Second)
}