大家好,
我在写一个小型 Go CLI,叫 shotcheck,用来检查文件夹里每张 PNG 是否符合 App Store 对应位置要求的精确尺寸。一个 worker 池(默认 8 个)从 jobs 里读文件名,检查后把 Result 发到无缓冲的 results channel。main 里的 collect 负责读取,遇到第一个不合格的就调用 cancel() 并返回错误。表面行为是对的,会打印 checked 11 of 40,然后输出 FAIL 和文件名,退出码为 1。
问题出在 -debug 参数上。失败之后我先短暂 sleep,再打印 runtime.NumGoroutine(),本以为是 1。结果 8 个 worker 时是 10,-workers 4 时是 6,也就是永远是 worker 数 + 2。sleep 时间加长(先 2 秒,再 5 秒)也没有变化。这是我凭上次运行的印象写的,因为我现在这台机器上跑不了 Go。
生产者 goroutine 确实停了,jobs 也关闭了,所以我不明白为什么 context 取消后,其余的 goroutine 没有停。我试过挪动 cancel() 的位置,没变化。我怀疑是 WaitGroup 的问题,或者 defer cancel() 执行得太晚。
在 CLI 里这无伤大雅,因为进程会退出。但我想把这个池放进一个长期运行的 CI 辅助程序里,那样的话我觉得每次失败的运行都会堆积这些 goroutine,把机器拖得很慢。
worker 部分的代码在下面。
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for path := range jobs {
r := checkShot(path)
results <- r
}
}()
}
go func() {
wg.Wait()
close(results)
}()
会不会是我漏掉了什么很简单的东西?感谢阅读))
从英语翻译·查看原文Hi all,
I am building a small Go CLI, shotcheck, that checks every PNG in a folder against the exact size the App Store slot wants. A pool of workers (8 by default) reads names from jobs, checks them and sends a Result into an unbuffered results channel. collect in main reads them, and on the first bad one calls cancel() and returns an error. Visible behaviour is right: it prints checked 11 of 40, then FAIL with the file name, exit code 1.
My problem is the -debug flag. After a failure I print runtime.NumGoroutine() after a short sleep and expected 1. I get 10 with 8 workers, and 6 with -workers 4, so always workers + 2. A longer sleep (2s, then 5s) changes nothing. I am writing this from memory of my last run, as I can't run Go on this machine right now.
The producer goroutine does stop and jobs is closed, so I don't understand why the canceled context did not stop the rest. I tried moving cancel() around, no change. I suspect the WaitGroup, or defer cancel() running too late.
In the CLI it is harmless because the process exits. But I want this pool inside a long-running CI helper, and there I think these goroutines would pile up on every failed run and slow the machine a lot.
The worker part:
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for path := range jobs {
r := checkShot(path)
results <- r
}
}()
}
go func() {
wg.Wait()
close(results)
}()
Is it possible that I am missing something simple here? Thanks for reading))