所有goroutines都陷入僵局

时间:2016-01-13 00:47:11

标签: go deadlock goroutine

这是我之前发表的帖子的后续内容:

    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()


    }

频道关闭的方式有什么问题?

1 个答案:

答案 0 :(得分:3)

你并没有真正关注你的管道设计。你必须问自己“X部分何时完成?完成后会发生什么?完成后会发生什么?”对于管道的每个部分。

您可以启动headtailworker来覆盖频道。这些函数成功返回的唯一方法是关闭这些函数。

从你需要的地方抽出来。

  1. head(in)参与in
  2. worker(out, in, &wg)的范围超过in,提取到out,并在wg关闭后告知您已完成in
  3. tail(out)范围超过out
  4. 那么您需要做什么:

    1. 确保处理所有输入?
    2. 确保所有goroutines都返回?
    3. 像这样:

      1. 完成处理所有文件后,您需要从in关闭head
      2. 这会导致worker在处理完in所有项目后实际返回,导致wg.Wait()返回
      3. 现在可以安全地关闭out,因为没有任何东西可以输入它,这将导致tail最终返回。
      4. 但是对于这个特定的设计,你可能需要另一个与sync.WaitGroup相关联的tail,因为整个程序将在wg.Wait()返回时退出,因此可能无法完成所有的工作tail正在做。 See here。具体做法是:

          

        程序执行从初始化主程序包开始,然后   调用函数main。当该函数调用返回时,   程序退出。它不等待其他(非主要)goroutines   完整。

        您可能还想使用缓冲通道referenced here来帮助不要在goroutines之间执行切换。使用您当前的设计,您将浪费大量时间进行上下文切换。