【问题标题】:Processing a channel concurrently results in unexpected output同时处理通道会导致意外输出
【发布时间】:2015-07-09 22:43:54
【问题描述】:

我有一个无缓冲通道,i 数量的工作人员从(文件系统路径)获取值并处理它(通过 HTTP 发送文件内容)。当我增加i时遇到问题。

当我运行这个时:

paths := make(chan string)

for i := 0; i < 5; i++ {
    go func() {
        for path := range paths {
            fmt.Println(path)
        }
    }()
}

walkFn := func(path string, info os.FileInfo, err error) error {
    if !info.IsDir() {
        paths <- path
    }
    return nil
}

filepath.Walk("/tmp/foo", walkFn)
close(paths)

它按预期工作并输出/tmp/foo的所有内容:

/tmp/foo/2
/tmp/foo/file9
/tmp/foo/file91
/tmp/foo/file90
/tmp/foo/file900
/tmp/foo/file901
/tmp/foo/file902
/tmp/foo/file92
/tmp/foo/file97
/tmp/foo/file93
/tmp/foo/file94
/tmp/foo/file95
/tmp/foo/file96
/tmp/foo/file98
/tmp/foo/file99

但是当我通过 HTTP 发送文件内容时,受影响的文件数量突然下降:

for i := 0; i < 5; i++ {
    go func() {
        for path := range paths {
            resp, err := http.Head("https://example.com/" + strings.TrimPrefix(path, rootDir+"/"))
            if err != nil {
                fmt.Printf("Error: %s\n", err)
                return
            }

            fmt.Printf("%s: %s\n", path, resp.Status)
        }
    }()
}

受影响文件的数量从 15 个(即目录中存在的数量)减少到 10 个:

/tmp/foo/2: 404 Not Found
/tmp/foo/file901: 404 Not Found
/tmp/foo/file900: 404 Not Found
/tmp/foo/file9: 404 Not Found
/tmp/foo/file90: 404 Not Found
/tmp/foo/file902: 404 Not Found
/tmp/foo/file91: 404 Not Found
/tmp/foo/file92: 404 Not Found
/tmp/foo/file93: 404 Not Found
/tmp/foo/file94: 404 Not Found

这是一个将i 的值与输出行数相关联的表格:

+-----+-------+
| `i` | lines |
+-----+-------+
| 1   | 15    |
| 5   | 10    |
| 6   | 9     |
| 15  | 0     |
+-----+-------+

为什么会发生这种情况?如何同时处理所有频道条目? http 请求有问题吗?

【问题讨论】:

  • 您的工人 return(停止工作)出错。会不会是他们迟早都会遇到错误,没有工人活着的问题?此外,您的程序是否阻止给工作人员足够的时间来完成或在所有工作完成之前退出主程序?
  • 嗯,但是如果每个工人都死于 return 声明,那么您肯定会在输出中看到 Error: 吗?关于程序块何时退出,我认为它会在paths 频道为空时自动完成 - 不是吗?

标签: concurrency go


【解决方案1】:

问题在于这一行之后:

filepath.Walk("/tmp/foo", walkFn)

所有路径都已通过paths 通道发送,这意味着有人收到了它们。但是,这并不意味着那些接收 goroutine 的人已经完全完成了。

所以当你的程序在close(paths) 之后退出时,仍然有 goroutines 在工作,它们会因为main 完成而被杀死。

https://golang.org/ref/spec#Program_execution

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

一个简单的解决方案是添加

select{}

在程序结束时。这将使它永远阻塞。

【讨论】:

  • 如果我希望它只阻止直到paths 频道为空,我该怎么做?
  • 我想通了 - 只需使用 sync.WaitGroup。谢谢指点!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-31
  • 2014-04-04
相关资源
最近更新 更多