在计算goroutine中的数据竞争时遇到困难



我最近开始学习围棋,我已经在这方面慢慢摸索了一段时间,但我觉得是时候寻求一些具体的帮助了。我有我的程序请求从api分页数据,因为有大约160页的数据。似乎是一个很好的使用程序,除了我有竞争条件,我似乎不知道为什么。这可能是因为我对语言不熟悉,但我的印象是函数的参数是作为调用它的函数中数据的副本传递的,除非它是指针。

根据我认为我知道这应该复制我的数据,这让我可以自由地在主函数中改变它,但我最终请求一些页面多次和其他页面只有一次。

我main.go

package main
import (
"bufio"
"encoding/json"
"log"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatalln(err)
}
httpClient := &http.Client{}
baseURL := "https://api.data.gov/ed/collegescorecard/v1/schools.json"
filters := make(map[string]string)
page := 0
filters["school.degrees_awarded.predominant"] = "2,3"
filters["fields"] = "id,school.name,school.city,2018.student.size,2017.student.size,2017.earnings.3_yrs_after_completion.overall_count_over_poverty_line,2016.repayment.3_yr_repayment.overall"
filters["api_key"] = os.Getenv("API_KEY")
outFile, err := os.Create("./out.txt")
if err != nil {
log.Fatalln(err)
}
writer := bufio.NewWriter(outFile)
requestURL := getRequestURL(baseURL, filters)
response := requestData(requestURL, httpClient)
wg := sync.WaitGroup{}
for (page+1)*response.Metadata.ResultsPerPage < response.Metadata.TotalResults {
page++
filters["page"] = strconv.Itoa(page)
wg.Add(1)
go func() {
defer wg.Done()
requestURL := getRequestURL(baseURL, filters)
response := requestData(requestURL, httpClient)
_, err = writer.WriteString(response.TextOutput())
if err != nil {
log.Fatalln(err)
}
}()
}
wg.Wait()
}
func getRequestURL(baseURL string, filters map[string]string) *url.URL {
requestURL, err := url.Parse(baseURL)
if err != nil {
log.Fatalln(err)
}
query := requestURL.Query()
for key, value := range filters {
query.Set(key, value)
}
requestURL.RawQuery = query.Encode()
return requestURL
}
func requestData(url *url.URL, httpClient *http.Client) CollegeScoreCardResponseDTO {
request, _ := http.NewRequest(http.MethodGet, url.String(), nil)
resp, err := httpClient.Do(request)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
var parsedResponse CollegeScoreCardResponseDTO
err = json.NewDecoder(resp.Body).Decode(&parsedResponse)
if err != nil {
log.Fatalln(err)
}
return parsedResponse
}

我知道我将遇到的另一个问题是以正确的顺序写入输出文件,但我相信使用通道告诉每个例程完成写入的请求可以解决这个问题。如果我错了,我希望你能告诉我如何处理这个问题。

提前感谢。

例程不接收数据的副本。当编译器检测到变量"转义"当前函数,它在堆上分配这个变量。在这种情况下,filters就是这样一个变量。当程序启动时,它访问的filters是与主线程相同的映射。因为你一直在主线程中修改filters而不加锁,所以不能保证线程程看到什么。

我建议您保持filters只读,通过复制filters中的所有项目在运行例程中创建一个新的映射,并将"page"添加到运行例程中。您必须小心传递page的副本:

go func(page int) {
flt:=make(map[string]string)
for k,v:=range filters {
flt[k]=v
}
flt["page"]=strconv.Itoa(page)
...
} (page)

相关内容

最新更新