Część, chciałem się was zapytać o małą pomoc, tak z ciekawości chciałem spróbować coś napisać wykorzystując go routine i tak sobie pomyślałem żeby odpytać API od github-a o rożne info dla niektórych repo.
W sumie nie wiem, czy to jest najlepszy przykład zęby wykorzystać go routine, ale wydaję mi się, że mając 4 procesory fizyczne wykorzystując go routine po prostu czas request-ów się zmniejszy, tak wygląda mniej więcej kod:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"runtime"
"sync"
"time"
)
type Repository struct {
Id int `json:"id"`
Name string `json:"full_name"`
ForksCount int `json:"forks_count"`
WatchersCount int `json:"watchers_count"`
Contributors []Contributor
Languages map[string]int
}
type Contributor struct {
Id int `json:"id"`
Url string `json:"url"`
Contributions int `json:"contributions"`
}
type ChannelContributors struct {
repo string
contributors []Contributor
}
type ChannelLanguages struct {
repo string
languages map[string]int
}
func (j *Repository) prettyPrint() string {
s, _ := json.MarshalIndent(j, "", "\t")
return string(s)
}
func (r *Repository) AddContributor(contributor Contributor) {
r.Contributors = append(r.Contributors, contributor)
}
func (r *Repository) AddLanguages(languages map[string]int) {
r.Languages = languages
}
func (r *Repository) getContributors(client http.Client, ch chan<- *ChannelContributors, wg *sync.WaitGroup) {
defer wg.Done()
req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s/contributors", "https://api.github.com/repos", r.Name), nil)
if err != nil {
ch <- nil
log.Fatalln(err)
}
resp, err := client.Do(req)
if err != nil {
ch <- nil
log.Fatalln(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
fmt.Printf("Contributors not found for repo %s | Status code: %d \n", r.Name, resp.StatusCode)
ch <- nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
var contributors []Contributor
err = json.Unmarshal(body, &contributors)
if err != nil {
log.Println("Contributors unmarshal error: ", err)
ch <- nil
}
fmt.Printf("Contributors for repo %s : %d \n", r.Name, len(contributors))
data := new(ChannelContributors)
data.repo = r.Name
for _, c := range contributors {
data.contributors = append(data.contributors, c)
}
ch <- data
}
func (r *Repository) getLanguages(client http.Client, ch chan<- *ChannelLanguages, wg *sync.WaitGroup) {
defer wg.Done()
req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s/languages", "https://api.github.com/repos", r.Name), nil)
if err != nil {
log.Fatalln(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
data := new(ChannelLanguages)
data.repo = r.Name
data.languages = nil
if resp.StatusCode != 200 {
fmt.Printf("Languages not found for repo %s | Status code: %d \n", r.Name, resp.StatusCode)
data.languages = nil
ch <- data
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
ch <- data
log.Fatalln(err)
}
languages := make(map[string]int)
err = json.Unmarshal(body, &languages)
if err != nil {
log.Println("Languages unmarshal error: ", err)
ch <- data
return
}
data.languages = languages
ch <- data
}
func main() {
start := time.Now()
numCPUs := runtime.NumCPU()
fmt.Printf("Available processors: %d \n", numCPUs)
runtime.GOMAXPROCS(numCPUs)
repos := map[string]string{
"DivanteLtd/vue-storefront": "https://api.github.com/repos/DivanteLtd/vue-storefront",
"chebyrash/promise": "https://api.github.com/repos/chebyrash/promise",
"dunglas/mercure": "https://api.github.com/repos/dunglas/mercure",
"flutter/flutter": "https://api.github.com/repos/flutter/flutter",
"api-platform/api-platform": "https://api.github.com/repos/api-platform/api-platform",
"Sylius/Sylius": "https://api.github.com/repos/Sylius/Sylius",
"gocolly/colly": "https://api.github.com/repos/gocolly/colly",
}
reposData := make(map[string]*Repository, len(repos))
timeout := time.Duration(2 * time.Second)
client := http.Client{
Timeout: timeout,
}
for k, repoUrl := range repos {
req, err := http.NewRequest("GET", repoUrl, nil)
if err != nil {
log.Fatalln(err)
}
req.Header.Set("Authorization", "token %TOKEN%")
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Fatalf("Cannot get info about %s repo | Status code: %d \n", k, resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalln(err)
}
repo := new(Repository)
err = json.Unmarshal(body, &repo)
if err != nil {
log.Fatalln(err)
}
reposData[k] = repo
}
contributorsChan := make(chan *ChannelContributors, len(reposData))
languagesChan := make(chan *ChannelLanguages, len(reposData))
wg := new(sync.WaitGroup)
for k, _ := range reposData {
wg.Add(len(reposData) * 2)
go reposData[k].getContributors(client, contributorsChan, wg)
go reposData[k].getLanguages(client, languagesChan, wg)
}
select {
case contributorResult := <-contributorsChan:
if len(contributorResult.contributors) > 0 {
for _, c := range contributorResult.contributors {
reposData[contributorResult.repo].AddContributor(c)
}
}
case langResult := <-languagesChan:
if langResult.languages != nil {
reposData[langResult.repo].AddLanguages(langResult.languages)
}
}
wg.Wait()
close(contributorsChan)
close(languagesChan)
for k, _ := range reposData {
fmt.Println(reposData[k].prettyPrint())
}
elapsed := time.Since(start)
log.Printf("Took %s", elapsed)
}
No i mam 2 problemy:
– Kod wykonuję się bez przerwy tak jakby wait group nigdy się nie kończył
– Czasami nigdy nie czyta z jednej z dwóch go routine.
Ogólnie nie rozumiem zbytnio co robię źle nigdy nie pisałem w językach, które pozwalają pracować na thread (no ok js, ale jest single threaded i tam asynchroniczność działa trochę inaczej).
Czy ktoś może mi podpowiedzieć co robię źle? Nie chce rozwiązania.
Dzięki z góry za pomoc.