我正在开发一个小脚本,它使用bufio.Scanner
和http.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)
}
来自流scanLineWise
的或多或少的预期将计算一个数字,而scalWordWise
将计为零。这是因为scanLineWise
已经从req.Body
读取了所有内容。
我想知道:如何优雅地解决这个问题?
我的第一个想法是构建一个实现io.Reader
和io.Writer
的结构。我们可以使用io.Copy
从req.Body
读取并将其写入writer
。当扫描仪从该编写器读取时,编写器将复制数据而不是读取数据。不幸的是,这只会随着时间的推移收集内存并打破整个流的想法......
答案 0 :(得分:15)
选项非常简单 - 您要么维护"流"数据,或缓冲身体。
如果你确实需要多次阅读身体,那么你需要在某处缓冲它。没有办法解决这个问题。
您可以通过多种方式传输数据,例如将行计数器输出行放入字计数器(最好通过通道)。您还可以使用io.TeeReader
和io.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
中进行实际阅读,然后将行传到scanWordWise
,example:
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)
}