我想实现一个带有n
工作人员的“爬虫”,每个工作人员都可以添加其他工作。当没有工作和所有工人完成工作时,程序应该停止。
我有以下代码(您可以在https://play.golang.org/p/_j22p_OfYv播放):
package main
import (
"fmt"
"sync"
)
func main() {
pathChan := make(chan string)
fileChan := make(chan string)
workers := 3
var wg sync.WaitGroup
paths := map[string][]string{
"/": {"/test", "/foo", "a", "b"},
"/test": {"aa", "bb", "cc"},
"/foo": {"/bar", "bbb", "ccc"},
"/bar": {"aaaa", "bbbb", "cccc"},
}
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
for {
path, ok := <-pathChan
if !ok {
break
}
for _, f := range paths[path] {
if f[0] == '/' {
pathChan <- f
} else {
fileChan <- f
}
}
}
wg.Done()
}()
}
pathChan <- "/"
for {
filePath, ok := <-fileChan
if !ok {
break
}
fmt.Println(filePath)
}
wg.Wait()
close(pathChan)
}
不幸的是,这以死锁结束。问题究竟在哪里?此外,编写此类功能的最佳做法是什么?渠道是否正确使用?
修改
我已更新我的代码以使用两个等待组,一个用于作业,另一个用于工作人员(请参阅https://play.golang.org/p/bueUJzMhqj):
package main
import (
"fmt"
"sync"
)
func main() {
pathChan := make(chan string)
fileChan := make(chan string)
jobs := new(sync.WaitGroup)
workers := new(sync.WaitGroup)
nworkers := 2
paths := map[string][]string{
"/": {"/test", "/foo", "a", "b"},
"/test": {"aa", "bb", "cc"},
"/foo": {"/bar", "bbb", "ccc"},
"/bar": {"aaaa", "bbbb", "cccc"},
}
for i := 0; i < nworkers; i++ {
workers.Add(1)
go func() {
defer workers.Done()
for {
path, ok := <-pathChan
if !ok {
break
}
for _, f := range paths[path] {
if f[0] == '/' {
jobs.Add(1)
pathChan <- f
} else {
fileChan <- f
}
}
jobs.Done()
}
}()
}
jobs.Add(1)
pathChan <- "/"
go func() {
jobs.Wait()
close(pathChan)
workers.Wait()
close(fileChan)
}()
for {
filePath, ok := <-fileChan
if !ok {
break
}
fmt.Println(filePath)
}
}
这似乎确实有效,但如果将nworkers
设置为1
,则显然会发生死锁,因为单个工作人员在向频道pathChan
添加内容时会永远等待。要解决此问题,可以增加通道缓冲区(例如pathChan := make(chan string, 2)
),但只有两个缓冲区未完全填满时才会起作用。当然,缓冲区大小可以设置为大数,比如说10000,但代码仍然可能遇到死锁。此外,这对我来说似乎不是一个干净的解决方案。
这是我意识到使用某种队列而不是通道更容易,其中可以无阻塞地添加和删除元素,并且队列的大小不固定。 Go标准库中是否存在此类队列?
答案 0 :(得分:0)
如果您想等待任意数量的工作人员完成,标准库包含sync.WaitGroup
就是为了这个目的。
还有其他并发问题:
有一些解决方案: