【问题标题】:Why does for loop with goroutines result in missing data为什么带有 goroutine 的 for 循环会导致数据丢失
【发布时间】:2020-02-02 19:16:40
【问题描述】:

好的,所以我有两段代码。首先是一个简单的 for 循环,效果很好

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "os"

    elasticsearch "github.com/elastic/go-elasticsearch/v7"
    "github.com/elastic/go-elasticsearch/v7/esapi"
    "github.com/mitchellh/mapstructure"
)

type Esindices struct {
    Health       string `json:"health"`
    Status       string `json:"status"`
    Index        string `json:"index"`
    Uuid         string `json:"uuid"`
    Pri          string `json:"pri"`
    Rep          string `json:"rep"`
    DocsCount    string `json:"docs.count"`
    DocsDeleted  string `json:"docs.deleted"`
    StoreSize    string `json:"store.size"`
    PriStoreSize string `json:"pri.store.size"`
}

func main() {

    var r []map[string]interface{}

    es, err := elasticsearch.NewDefaultClient()
    if err != nil {
        log.Fatalf("Error creating client: %s", err)
    }

    req := esapi.CatIndicesRequest{
        Format: "json",
        Pretty: false,
    }

    res, err := req.Do(context.Background(), es)
    if err != nil {
        log.Fatalf("Error getting response: %s", err)
    }

    defer res.Body.Close()

    if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
        log.Printf("Error parsing the response body: %s", err)
    }

    indexSlice := make([]*Esindices, len(r))

    for i, element := range r {
        result := &Esindices{}
        cfg := &mapstructure.DecoderConfig{
            Metadata: nil,
            Result:   &result,
            TagName:  "json",
        }
        decoder, _ := mapstructure.NewDecoder(cfg)
        decoder.Decode(element)
        indexSlice[i] = result
    }

    thisisjson, err := json.MarshalIndent(indexSlice, "", " ")
    if err != nil {
      log.Fatal("Can't encode to JSON", err)
    }


    fmt.Fprintf(os.Stdout, "%s", thisisjson)

大部分内容都是不言自明的,但只是为了澄清一下,我正在使用 Elasticsearch 客户端和 api.cat.indices API 来获取本地 Elasticsearch 安装中所有索引的列表,然后将它们存储为数组的map[string]interface{} 然后循环它以将它们添加到结果结构的切片中。实际上这很好,但我想注意性能,虽然我不能改善请求本身的延迟,但我当然可以改善循环的性能,至少我认为我应该能够做到。

所以当我尝试以下方法时,我得到了奇怪的结果。

var wg sync.WaitGroup
defer wg.Wait()
for i, element := range r {
    wg.Add(1)
    go func(i int, element map[string]interface{}) {
        defer wg.Done()
        result := Esindices{}
        cfg := &mapstructure.DecoderConfig{
            Metadata: nil,
            Result:   &result,
            TagName:  "json",
        }
        decoder, _ := mapstructure.NewDecoder(cfg)
        decoder.Decode(element)
        indexSlice[i] = result
    }(i, element)
}

问题是,具体来说,切片中元素的键的某些值是空的。这让我觉得代码正在尝试添加到切片中,但即使它没有完成它也会通过。

想法?

【问题讨论】:

    标签: go struct goroutine


    【解决方案1】:

    在 for 循环的末尾使用 wg.Wait 代替 defer wg.Wait。在 for 循环完成后,您正在使用由 for 循环中的 goroutines 构造的数据,并且在使用该数据之前您无需等待所有 goroutines 完成。

    当您使用defer wg.Wait 时,等待发生在函数的末尾。使用数据的 for 循环对不完整的数据进行操作,因为 goroutine 仍在运行。

    当你在for循环结束时使用wg.Wait时,你首先等待所有goroutines结束,然后使用它们生成的数据。

    【讨论】:

    • 做到了!您介意最后多推论一下defer wg.Wait()wg.Wait() 之间的区别吗?
    • 内容丰富。我认为这对其他人会有所帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-28
    • 1970-01-01
    • 2014-03-25
    • 2022-01-04
    • 2022-12-05
    • 1970-01-01
    • 2013-04-30
    相关资源
    最近更新 更多