在goroutine中找出数据竞争的问题
我最近开始学习围棋,我已经花了一段时间在这方面,但我想是时候寻求一些具体的帮助了。我的程序从api请求分页数据,因为有大约160页的数据。似乎是goroutines的一个很好的用法,除了我有比赛条件,我似乎不知道为什么。这可能是因为我不熟悉这种语言,但我的印象是,函数的参数在调用它的函数中作为数据的副本传递,除非它是指针 据我所知,这应该是复制我的数据,让我可以在主功能中自由更改它,但我最终多次请求某些页面,而其他页面仅请求一次 我的主要任务是去在goroutine中找出数据竞争的问题,go,concurrency,goroutine,Go,Concurrency,Goroutine,我最近开始学习围棋,我已经花了一段时间在这方面,但我想是时候寻求一些具体的帮助了。我的程序从api请求分页数据,因为有大约160页的数据。似乎是goroutines的一个很好的用法,除了我有比赛条件,我似乎不知道为什么。这可能是因为我不熟悉这种语言,但我的印象是,函数的参数在调用它的函数中作为数据的副本传递,除非它是指针 据我所知,这应该是复制我的数据,让我可以在主功能中自由更改它,但我最终多次请求某些页面,而其他页面仅请求一次 我的主要任务是去 package main import (
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
}
主程序包
进口(
“布菲奥”
“编码/json”
“日志”
“net/http”
“网络/网址”
“操作系统”
“strconv”
“同步”
“github.com/joho/godotenv”
)
func main(){
错误:=godotenv.Load()
如果错误!=零{
log.Fatalln(错误)
}
httpClient:=&http.Client{}
baseURL:=”https://api.data.gov/ed/collegescorecard/v1/schools.json"
过滤器:=make(映射[字符串]字符串)
页码:=0
过滤器[“学校授予的学位”]=“2,3”
过滤器[“字段”]=“id,学校。名称,学校。城市,2018年。学生。规模,2017年。学生。规模,2017年。收入。完成后3年。总体贫困线,2016年。还款。3年还款。总体”
过滤器[“api_键”]=os.Getenv(“api_键”)
outFile,err:=os.Create(“./out.txt”)
如果错误!=零{
log.Fatalln(错误)
}
writer:=bufio.NewWriter(outFile)
requestURL:=getRequestURL(baseURL,筛选器)
响应:=请求数据(请求URL,httpClient)
wg:=sync.WaitGroup{}
对于(第+1页)*response.Metadata.ResultsPerPage
我知道我将遇到的另一个问题是以正确的顺序写入输出文件,但我相信使用通道告诉每个例程完成了什么请求可以解决这个问题。如果我在这一点上不正确,我也会很感激任何关于如何处理这一问题的建议
提前感谢。goroutines不接收数据副本。当编译器检测到一个变量“转义”当前函数时,它会在堆上分配该变量。在这种情况下,
过滤器
就是这样一个变量。当goroutine启动时,它访问的过滤器
与主线程的映射相同。由于您一直在主线程中修改过滤器
,而没有锁定,因此无法保证goroutine会看到什么
我建议您保持过滤器
只读,通过复制过滤器
中的所有项目,在goroutine中创建一个新映射,并在goroutine中添加“页面”
。您还必须小心传递页面的副本
:
go func(page int) {
flt:=make(map[string]string)
for k,v:=range filters {
flt[k]=v
}
flt["page"]=strconv.Itoa(page)
...
} (page)
您也不能同时写入
编写器。Go类型对于并发使用是不安全的,除非有文档记录。