当我在goroutine中运行wg.Wait()时,为什么我的代码正常工作?

时间:2017-10-04 08:21:49

标签: asynchronous go goroutine

我有一个我正在抓取的网址列表。我想要做的是将所有成功抓取的页面数据存储到一个通道中,当我完成后,将其转储到一个切片中。我不知道我会获得多少成功的提取,所以我不能指定固定的长度。我希望代码达到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()。是否与在我的频道中接收值有关?

2 个答案:

答案 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之后的主线程中。

只有在所有goroutine返回后,

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}}

这样你就不能在通道中累积所有数据并使其超载,而是在数据到达时将数据放入目标切片。