这是我之前发表的帖子的后续内容:
http://stackoverflow.com/questions/34736825/goroutine-exit-status-2-what-does-it-mean-why-is-it-happening?noredirect=1#comment57238789_34736825
在阅读SO和SO上的多个主题和文章之后,我仍然无法弄清楚渠道应该关闭的位置。
该程序将打开文件列表,为每个输入文件(具有相同名称)创建输出文件,访问每个输入文件中的所有URL并从中获取所有href链接 - 这些链接保存到相应的输出文件。 但是,我收到以下错误:
http://play.golang.org/p/8X-1rM3aXC
linkgetter和getHref函数主要用于处理。头部和尾部作为单独的goroutines运行,工人进行处理。
package main
import (
"bufio"
"bytes"
"fmt"
"golang.org/x/net/html"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"regexp"
"sync"
)
type Work struct {
Link string
Filename string
}
type Output struct {
Href string
Filename string
}
func getHref(t html.Token) (href string, ok bool) {
// Iterate over all of the Token's attributes until we find an "href"
for _, a := range t.Attr {
if a.Key == "href" {
href = a.Val
ok = true
}
}
return
}
func linkGetter(out chan<- Output, r io.Reader, filename string) {
z := html.NewTokenizer(r)
for {
tt := z.Next()
switch {
case tt == html.ErrorToken:
return
case tt == html.StartTagToken:
t := z.Token()
isAnchor := t.Data == "a"
if !isAnchor {
continue
}
// Extract the href value, if there is one
url, ok := getHref(t)
if !ok {
continue
}
out <- Output{url, filename}
}
}
}
func worker(out chan<- Output, in <-chan Work, wg *sync.WaitGroup) {
defer wg.Done()
for work := range in {
resp, err := http.Get(work.Link)
if err != nil {
continue
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
continue
}
if err = resp.Body.Close(); err != nil {
fmt.Println(err)
}
linkGetter(out, bytes.NewReader(body), work.Filename)
}
}
func head(c chan<- Work) {
r, _ := regexp.Compile("(.*)(?:.json)")
files, _ := filepath.Glob("*.json")
for _, elem := range files {
res := r.FindStringSubmatch(elem)
for k, v := range res {
if k == 0 {
outpath, _ := filepath.Abs(fmt.Sprintf("go_tester/%s", v))
abspath, _ := filepath.Abs(fmt.Sprintf("url_links/%s", v))
f, _ := os.Open(abspath)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
c <- Work{outpath, scanner.Text()}
}
}
}
}
}
func tail(c <-chan Output) {
currentfile := ""
var f *os.File
var err error
for out := range c {
if out.Filename != currentfile {
if err = f.Close(); err != nil { // might cause an error on first run
fmt.Println(err)
}
f, err = os.OpenFile(out.Filename, os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
}
currentfile = out.Filename
}
if _, err = f.WriteString(out.Href + "\n"); err != nil {
fmt.Println(err)
}
}
}
const (
nworkers = 80
)
func main() {
//fmt.Println("hi")
in := make(chan Work)
out := make(chan Output)
go head(in)
go tail(out)
var wg sync.WaitGroup
for i := 0; i < 85; i++ {
wg.Add(1)
go worker(out, in, &wg)
}
close(in)
close(out)
wg.Wait()
}
频道关闭的方式有什么问题?
答案 0 :(得分:3)
你并没有真正关注你的管道设计。你必须问自己“X部分何时完成?完成后会发生什么?完成后会发生什么?”对于管道的每个部分。
您可以启动head
,tail
和worker
来覆盖频道。这些函数成功返回的唯一方法是关闭这些函数。
从你需要的地方抽出来。
head(in)
参与in
worker(out, in, &wg)
的范围超过in
,提取到out
,并在wg
关闭后告知您已完成in
tail(out)
范围超过out
那么您需要做什么:
像这样:
in
关闭head
。 worker
在处理完in
所有项目后实际返回,导致wg.Wait()
返回out
,因为没有任何东西可以输入它,这将导致tail
最终返回。 但是对于这个特定的设计,你可能需要另一个与sync.WaitGroup
相关联的tail
,因为整个程序将在wg.Wait()
返回时退出,因此可能无法完成所有的工作tail
正在做。 See here。具体做法是:
程序执行从初始化主程序包开始,然后 调用函数main。当该函数调用返回时, 程序退出。它不等待其他(非主要)goroutines 完整。
您可能还想使用缓冲通道referenced here来帮助不要在goroutines之间执行切换。使用您当前的设计,您将浪费大量时间进行上下文切换。