我有一个我正在抓取的网址列表。我想要做的是将所有成功抓取的页面数据存储到一个通道中,当我完成后,将其转储到一个切片中。我不知道我会获得多少成功的提取,所以我不能指定固定的长度。我希望代码达到wg.Wait()
,然后等到调用所有wg.Done()
方法,但我从未到达close(queue)
语句。寻找类似的答案,我遇到了这个SO答案
https://stackoverflow.com/a/31573574/5721702
作者做了类似的事情:
ports := make(chan string)
toScan := make(chan int)
var wg sync.WaitGroup
// make 100 workers for dialing
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for p := range toScan {
ports <- worker(*host, p)
}
}()
}
// close our receiving ports channel once all workers are done
go func() {
wg.Wait()
close(ports)
}()
我将wg.Wait()
包裹在goroutine中后,close(queue)
已到达:
urls := getListOfURLS()
activities := make([]Activity, 0, limit)
queue := make(chan Activity)
for i, activityURL := range urls {
wg.Add(1)
go func(i int, url string) {
defer wg.Done()
activity, err := extractDetail(url)
if err != nil {
log.Println(err)
return
}
queue <- activity
}(i, activityURL)
}
// calling it like this without the goroutine causes the execution to hang
// wg.Wait()
// close(queue)
// calling it like this successfully waits
go func() {
wg.Wait()
close(queue)
}()
for a := range queue {
// block channel until valid url is added to queue
// once all are added, close it
activities = append(activities, a)
}
如果我没有为close
使用goroutine,为什么代码无法到达wg.Wait()
?我认为所有的defer wg.Done()
语句都被调用,所以最终它会被清除,因为它会进入wg.Wait()
。是否与在我的频道中接收值有关?
答案 0 :(得分:4)
您需要等待goroutine在单独的线程中完成,因为需要读取queue
。执行以下操作时:
queue := make(chan Activity)
for i, activityURL := range urls {
wg.Add(1)
go func(i int, url string) {
defer wg.Done()
activity, err := extractDetail(url)
if err != nil {
log.Println(err)
return
}
queue <- activity // nothing is reading data from queue.
}(i, activityURL)
}
wg.Wait()
close(queue)
for a := range queue {
activities = append(activities, a)
}
queue <- activity
以来queue
的每个goroutine块都是无缓冲的,没有任何东西从它读取数据。这是因为queue
上的范围循环位于wg.Wait
之后的主线程中。
wg.Wait
才会解锁。但如上所述,所有goroutine都在通道发送时被阻止。
当您使用单独的goroutine等待时,代码执行实际到达queue
上的范围循环。
// wg.Wait does not block the main thread.
go func() {
wg.Wait()
close(queue)
}()
这导致goroutines在queue <- activity
语句处解锁(主线程开始读取queue
)并运行直到完成。反过来调用每个人wg.Done
。
等待goroutine越过wg.Wait
后,queue
将关闭,主线程将退出其上的范围循环。
答案 1 :(得分:1)
wg.Wait
通道是无缓冲的,因此每个尝试写入它的goroutine都会被阻止,因为读取器进程尚未启动。所以没有goroutinte可以写,并且它们都挂起 - 结果go func() {
for a := range queue {
// block channel until valid url is added to queue
// once all are added, close it
activities = append(activities, a)
}
}()
永远等待。
尝试在单独的goroutine中启动阅读器:
wg.Wait()
close(queue)
然后开始服务员:
{{1}}
这样你就不能在通道中累积所有数据并使其超载,而是在数据到达时将数据放入目标切片。