首页 > 解决方案 > 如何在循环中使用 gorutines 来填充结构?

问题描述

有这样的模型:

type ChromeBasedDirections struct {
    CurrencyFromName string
    CurrencyToName   string
    URL              string
    ParseResponse    ParseResponse
}

type ParseResponse struct {
    CurrentPrice float64
    Change24H    float64
    Err          error
}

type ParseResponseChan struct {
    URL          string
    CurrentPrice float64
    Change24H    float64
    Err          error
}

函数是这样的:

func ParseBNCByURL(u string, chanResponse chan models.ParseResponseChan) {
    var parseResponse models.ParseResponseChan
    //..
    // code that fill up parseResponse
    //..
    if err != nil {
        parseResponse.Err = err
        chanResponse <- parseResponse
        return

    }
    parseResponse.CurrentPrice = price
    parseResponse.Change24H = change24H
    chanResponse <- parseResponse
    return
}

这就是函数调用的方式:

func initParserMultiTread() {
    var urls = []string{"url_0","url_1","url_2","url_n"} // on production it will be taken from the json file
    var chromeBasedDirections []models.ChromeBasedDirections
    for _, url := range urls {
        chromeBasedDirections = append(chromeBasedDirections, models.ChromeBasedDirections{
            CurrencyFromName: "",
            CurrencyToName:   "",
            URL:              url,
            ParseResponse:    models.ParseResponse{},
        })
    }
    var parseResponseChan = make(chan models.ParseResponseChan) // here's how to do without hardcode?
    for _, dir := range chromeBasedDirections {
        go controllers.ParseBNCByURL(dir.URL, parseResponseChan)
    }
}

如果initParserMultiTread被执行,函数将在ParseBNCByURL执行之前完成。这是因为通道尚未被读取。如果可以使用硬编码,那么每个 url_n 都可以创建自己的 chan models.ParseResponseChan,然后在循环中比较ChromeBasedDirections.URLand ParseResponseChan.URL,如果匹配则填写ChromeBasedDirections.ParseResponse.

但我需要避免硬编码并循环执行所有操作。一般来说,这远不是第一个选项,因为我尝试对ParseBNCByURL函数进行多线程执行。

也就是有一个ChromeBasedDirections填充了CurrencyFrom/ToNameURL字段的模型,我需要ParseBNCByURL在多线程中执行,会填充ParseResponse.

我又试了一次,但该功能在多线程模式下不起作用:

var urls = []string{"url_0","url_1","url_2","url_n"} 
var chromeResults []models.ChromeBasedDirections
for _, url := range urls {
    var chromeResult = make(chan models.ChromeBasedDirections)
    go controllers.ParseBNCByURL(url, chromeResult)
    chromeResults = append(chromeResults, <-chromeResult)
}

标签: go

解决方案


您的代码的问题是您没有在parseResponseChan任何地方阅读。你所有的 goroutines 都会因此被阻塞,这就是initParserMultiTread函数在你的 goroutines 之前完成的原因。

您需要添加用于读取parseResponseChan通道的代码,以及用于在parseResponseChan所有 goroutine 执行完成后关闭通道的代码。

一种解决方案是与sync.WaitGroup.

首先改变你的ParseBNCByURL功能。这个函数需要在完成时发送一个信号。

func ParseBNCByURL( wg *sync.WaitGroup, u string, chanResponse chan models.ParseResponseChan) {
    defer wg.Done() //this signals when the goroutine is done
    
//the rest of the function's body

initParserMultiTread函数中,您需要一种方法来等待来自所有 goroutine 的信号以关闭parseResponseChan通道并从该通道读取响应。

func initParserMultiTread() {
    var urls = []string{"url_0","url_1","url_2","url_n"} // on production it will be taken from the json file
    var chromeBasedDirections []models.ChromeBasedDirections
    var wg sync.WaitGroup
    wg.Add(len(urls)) // add how many goroutines will be initialized
    for _, url := range urls {
            chromeBasedDirections = append(chromeBasedDirections, models.ChromeBasedDirections{
                    CurrencyFromName: "",
                    CurrencyToName:   "",
                    URL:              url,
                    ParseResponse:    models.ParseResponse{},
            })
    }

    var parseResponseChan = make(chan models.ParseResponseChan)

        //init a goroutine for closing the parseResponseChan once all goroutines are done
        go func(wg *sync.WaitGroup, ch chan models.ParseResponseChan) {
            wg.Wait() //wait for all goroutines to send the signal
            close(ch) //close the channel
        }(&wg, parseResponseChan)

    for _, dir := range chromeBasedDirections {
            go controllers.ParseBNCByURL(&wg, dir.URL, parseResponseChan)
    }

    //read the responses from the channel
    for response := range parseResponseChan {
        //some code to handle the responses
    }
}

一旦所有的 goroutine 都被初始化,你就开始从parseResponseChan通道中读取。这将阻止initParserMultiTread函数返回,直到所有 goroutine 都执行完毕并且parseResponseChan通道关闭。


推荐阅读