ããã«ã¡ã¯ãéèãœãªã¥ãŒã·ã§ã³äºæ¥éšã®å®®åã§ããæ¬èšäºã¯ é»éåœéæ
å ±ãµãŒãã¹ Advent Calendar 2023 7æ¥ç®ã®èšäºãšãªããŸãã ã¿ãªãããGoèšèªã§äžŠè¡åŠçã¯å©çšãããŠããã§ããããïŒGoèšèªã¯ãŽã«ãŒãã³ããã£ãã«ãšãã£ãç¬èªã®äžŠè¡åŠçæ©æ§ãåããŠãããæ¯èŒçç°¡åã«äžŠè¡åŠçãå°å
¥ã§ããŸãã ããããªããããŽã«ãŒãã³ããã£ãã«ã®ä»çµã¿ãçè§£ãã䞊è¡åŠçã®ãã¿ãŒã³ãç¬èªã«å®è£
ããŠããã®ã¯æå€ãšé£ãããã®ã§ãã Goèšèªã§ã¯syncããã±ãŒãžãã¯ãããšããŠã䞊è¡åŠçã®ãã¿ãŒã³ãç°¡åã«å°å
¥ã§ããããã«ã©ã€ãã©ãªãšããŠæäŸããŠããŸãã æ¬èšäºã§ã¯GoèšèªãæäŸããŠããã©ã€ãã©ãªã®äžã§ãæºæšæºããã±ãŒãžã®äœçœ®ä»ãã§ãã golang.org/x/sync/errgroup ããã±ãŒãžãå©çšãã䞊è¡åŠçã®å®è£
æ¹æ³ãã玹ä»ããããšæããŸãã åæ ãŸãã¯ã·ã³ãã«ã«èšè¿°ããŠã¿ã ãšã©ãŒãèæ
®ããªãã颿°åããŠã¿ã ãã£ã³ã»ã«ãæ€èšããŠã¿ã åæå®è¡æ°ãå¶åŸ¡ãã ãŸãšã åæ ä»¥éã§ã¯ãç¹å®ã®URLã«HTTPãªã¯ ãšã¹ ãã䞊è¡ã§éä¿¡ããªãããå
šãŠã®URLãžã®HTTPãªã¯ ãšã¹ ããçµãããŸã§åŸ
ã¡åãããããšããåŠçãåæãšããŠãµã³ãã«ã³ãŒããèšèŒããŸãã ä»åã®ãµã³ãã«ã³ãŒãã¯syncããã±ãŒãžã® ãµã³ãã«ã³ãŒã ãäžéšæ¹å€ããŠäœæããŠããŸãã ãŸããGoã®ããŒãžã§ã³ã«ã€ããŠã¯1.21.3ãåæãšããŠããŸãã ãŸãã¯ã·ã³ãã«ã«èšè¿°ããŠã¿ã package main import ( "fmt" "net/http" "sync" ) func main() { var wg sync.WaitGroup var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } for i, url := range urls { i, url := i, url wg.Add( 1 ) go func () { defer wg.Done() resp, err := http.Get(url) if err != nil { fmt.Println(err) return } defer resp.Body.Close() fmt.Printf( "%dçªç®ã®ãªã¯ãšã¹ã: %s \n " , i+ 1 , resp.Status) }() } wg.Wait() } ãŸãã¯ã·ã³ãã«ãªåœ¢ã§åŠçãå®è£
ããŠã¿ãŸããæåã®äŸã§ã¯errgroupããã±ãŒãžã¯å©çšãããsyncããã±ãŒãžã®sync.WaitGroupãå©çšããŠåŠçãèšè¿°ããŸãã åŸã»ã©è©³ãã説æããŸãããerrgroupããã±ãŒãžã¯sync.WaitGroupã®æ©èœãæ¡åŒµãããã®ã«ãªããŸãããã®ããããŸãã¯sync.WaitGroupãããçè§£ããããšãéèŠã§ãã åŠçãšããŠã¯forã«ãŒããšgoããŒã¯ãŒããå©çšããŽã«ãŒãã³ãèµ·åããŠã3ã€ã®URLã«å¯ŸããŠäžŠè¡ã«ãªã¯ ãšã¹ ããéä¿¡ããŠããŸãã sync.WaitGroupãçè§£ããäžã§éèŠãªã®ã¯Addã¡ãœãããDoneã¡ãœãããWaitã¡ãœããã§ãã ä»åã®ããã°ã©ã ã§ã¯goããŒã¯ãŒãã§ãŽã«ãŒãã³ãèµ·åããåã«Addã¡ãœãããåŒã³åºããåŠçãçµãã£ãåŸã«Doneã¡ãœãããåŒã³åºããŠããŸãã ãããŠã«ãŒãã®å€åŽã§Waitã¡ãœãããåŒã³åºãããã¹ãŠã®ãŽã«ãŒãã³ãå®äºããã®ãåŸ
ã¡ãŸãã sync.WaitGroupã§ã¯Addã¡ãœããã§å
éšã®ã«ãŠã³ã¿ãã€ã³ã¯ãªã¡ã³ãããDoneã¡ãœããã§å
éšã®ã«ãŠã³ã¿ããã¯ãªã¡ã³ãããŸãã ãããŠWaitã¡ãœããã§ã¯å
éšã®ã«ãŠã³ã¿ãç£èŠããã«ãŠã³ã¿ã0ã«ãªããŸã§åŸ
ã€ããã«ãªã£ãŠããŸãã å®è¡çµæã¯ä»¥äžã§ããä»åã®å®è¡ã§ã¯2çªç®ã3çªç®ã1çªç®ã®é ã§åŠçãå®äºããããã§ããåŠçã䞊è¡ã«è¡ãããŠããããããã®åºåã¯å®è¡æ¯ã«å€ããå¯èœæ§ããããŸãã 2 çªç®ã®ãªã¯ãšã¹ã: 200 OK 3 çªç®ã®ãªã¯ãšã¹ã: 200 OK 1 çªç®ã®ãªã¯ãšã¹ã: 200 OK sync.WaitGroupãå©çšãããšã·ã³ãã«ã«äžŠè¡åŠçãèšè¿°ã§ããŸãããã£ãã«ã®åŠçãèæ
®ããå¿
èŠããªããåŠçã®æµããæ¯èŒçã€ã¡ãŒãžããããã§ãã ãšã©ãŒãèæ
®ããªãã颿°åããŠã¿ã å
ã»ã©ã®äŸã§ã¯sync.WaitGroupãå©çšããŠåŠçãèšè¿°ããŸããã ä»åºŠã¯URLã䞊è¡ã§åŠçããéšåã颿°åããäŸãèããŠã¿ãŸãããã å®è£
ããŠããäžã§ç¹å®ã®åŠçã颿°åããããªã£ããããšã©ãŒãã³ããªã³ã°ãããããªã£ããããããšã¯ãããããŸãã ä»åã¯ä»¥äžã®ãããªé¢æ°ãèããŠã¿ãŸã func CallURL(urls [] string ) error ãªã¯ ãšã¹ ãå
ã®URLãåŒæ°ã«ãšããURLã«å¯ŸããŠäžŠè¡ã«åŠçãè¡ããããªCallURLãšãã颿°ãèããŠã¿ãŸãã ãŸãã颿°å
ã§ãšã©ãŒãçºçããå Žåã«ã¯ãšã©ãŒãäžäœã®é¢æ°ã«æž¡ããŸãã ãããæ©ãŸããã®ããšã©ãŒã®æ±ãã§ããgoããŒã¯ãŒãã䜿ã£ã颿°ããçŽæ¥ãšã©ãŒãåãåãããšã¯ã§ããŸããããsync.WaitGroupã®Waitã¡ãœãããå©çšããŠãšã©ãŒãååŸããããšãã§ããŸããã ããã§äŸ¿å©ãªã®ã golang.org/x/sync/errgroup ããã±ãŒãžã®errgroup.Groupã§ãã errgroup.Groupãå©çšããããšã§sync.WaitGroupãšåçã®æ©èœãå©çšããªãããšã©ãŒãã³ããªã³ã°ãè¡ãããšãå¯èœã§ãããã®errgroup.Groupãå©çšããŠäžèšã®é¢æ°ãèšè¿°ããŠã¿ãŸãã package main import ( "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { var eg errgroup.Group for i, url := range urls { i, url := i, url eg.Go( func () error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%dçªç®ã®ãªã¯ãšã¹ã: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } errgroup.Groupã¯sync.WaitGroupãå
éšã«æã€æ§é äœã§ãã errgroup.Groupãå©çšããä»åã®ãµã³ãã«ã³ãŒãã§ã¯Addã¡ãœãããDoneã¡ãœããããŽã«ãŒãã³ã®èµ·ååŠçã¯èšèŒããŠããŸããã ãããã®åŠçã¯å
šãŠGoã¡ãœããã®äžã§åŒã³åºãããŠãããããæç€ºçã«åŒã³ã ãå¿
èŠã¯ãããŸããã Goã¡ãœããã«æž¡ãã颿°ã¯å
éšã§goããŒã¯ãŒãã䜿ã£ãŠäžŠè¡ã«èµ·åããããã«ãªã£ãŠããŸãã ãŸããGoã¡ãœããã«ã¯ãšã©ãŒãè¿ãå€ãšãã颿°ãæž¡ããWaitã¡ãœããã§ã¯é¢æ°ããã®ãšã©ãŒãåãåããããã«ãªã£ãŠããŸããGoã¡ãœããã§èµ·åãã颿°ã®äžã§ãšã©ãŒãçºçããå Žåã«Waitã¡ãœããçµç±ã§ãšã©ãŒãåãåãããšãã§ããŸãã ããã§ãã€ã³ããªã®ããè€æ°ã®ãšã©ãŒãçºçããå Žåã§ãWaitã¡ãœããã§ç¢ºèªã§ãããšã©ãŒã¯äžã€ã ããšããããšã§ãã Waitã¡ãœããã§ã¯è€æ°ã®é¢æ°ã®äžã§æåã«çºçãããšã©ãŒãã確èªããããšãã§ããããã®ä»ã®ãšã©ãŒã«ã€ããŠã¯ç¢ºèªããããšã¯ã§ããŸããã ãã£ã³ã»ã«ãæ€èšããŠã¿ã å
ã»ã©ã®äŸã§ã¯äžã€ã®é¢æ°ã§ãšã©ãŒãçºçããŠããä»ã®é¢æ°ã¯ãã®ãšã©ãŒãæ°ã«ããåŠçãç¶ããããã«ãªã£ãŠããŸããã ããããªããå Žåã«ãã£ãŠã¯ãšã©ãŒãçºçããå Žåã«ä»ã®é¢æ°ã®åŠçã忢ããããããã£ã³ã»ã«ãããå ŽåããããšæããŸãã å®ã¯ãã£ã³ã»ã«ã«é¢ããæ©èœãerrgroup.Groupã«ã¯åãã£ãŠããŸãã äžã€ã®é¢æ°ã§ãšã©ãŒãçºçããå Žåã«ãä»ã®é¢æ°ã®åŠçã忢ãããµã³ãã«ãäœæããŠã¿ãŸãã package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%dçªç®ã®ãªã¯ãšã¹ã: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } ä»åã®äŸã§ã¯errgroup.WithContext颿°ãæåã«åŒã³åºããŠããŸãã è¿ãå€ã¯errgroup.Groupãšcontext.Contextã§ãã ããšã¯è¿ãå€ã®errgroup.Groupãšcontext.Contextãå©çšããŠãåŠçãå®è£
ããŸãã ä»åã®äŸã§ã¯ API åŒã³åºãéšåãå°ãå€ãã£ãŠããŸããhtttp.NewRequestWithContext颿°ãšhttp.DefaultClient.Doã¡ãœãããåŒã³ã ãããã«ããŠããŸãã ãããã®é¢æ°ãå©çšããããšã§åŒæ°ã®context.Contextããã£ã³ã»ã«ãããŠããããç£èŠããªããåŠçãé²ããããã«ããŠããŸãã http.DefaultClient.Doã¡ãœããå
éšã§ã¯context.Contextã®ãã£ã³ã»ã«ãç£èŠãããã£ã³ã»ã«ãããŠããå Žåã¯åŠçãçµäºããããã«ãªã£ãŠããŸãã ã§ã¯èå¿ã®context.Contextã®ãã£ã³ã»ã«åŠçã¯ã©ãã§å®è¡ãããŠããã®ã§ããããïŒ å®ã¯Goã¡ãœããã®å
éšã§é¢æ°ããšã©ãŒãè¿ããå Žåã«context.Contextã®ãã£ã³ã»ã«ãå®è¡ããããã«ãªã£ãŠããŸãã WithContext颿°ã®äžã§ã¯context.WithCancelCause颿°ãåŒã³åºãããŠãããWithCancelCause颿°ã®è¿ãå€ã®ãã£ã³ã»ã«é¢æ°ãerrgroup.Groupã®ãã£ãŒã«ãã«ã»ãããããŸãã Goã¡ãœããã®å
éšã§ã¯ãšã©ãŒãçºçããå Žåã«ããã®ãã£ã³ã»ã«é¢æ°ãåŒã³ã ãããšã§context.Contextããã£ã³ã»ã«ããããã«ãªã£ãŠããŸãã ïŒGoã®ããŒãžã§ã³ã1.20以äžã®å ŽåãWithContext颿°ã®å
éšã§ã¯context.WithCancelCause颿°ãåŒã³åºãããŸããã1.20以åã®å Žåã¯context.WithCancel颿°ãåŒã³åºãããŸãïŒ åæå®è¡æ°ãå¶åŸ¡ãã errgroup.Groupã«ã¯Goã¡ãœããã§èµ·åãããŽã«ãŒãã³ã®åæå®è¡æ°ãå¶åŸ¡ããæ©èœããããŸãã äŸãã°ç¹å®ã®URLã«è€æ°ã®ãªã¯ ãšã¹ ããæããéã«ãè² è·ãèæ
®ããªãããªã¯ ãšã¹ ããæãããå Žåãªã©ããããšæããŸãããããã£ãéã«ã¯ãã®åæå®è¡æ°å¶åŸ¡ã®ä»çµã¿ã圹ã«ç«ã¡ãŸãã ãµã³ãã«ã³ãŒããèŠãŠã¿ãŸãããã package main import ( "context" "fmt" "net/http" "golang.org/x/sync/errgroup" ) func main() { var urls = [] string { "http://www.golang.org/" , "http://www.google.com/" , "http://www.example.com/" , } if err := CallURL(urls); err != nil { fmt.Println(err) } } func CallURL(urls [] string ) error { eg, ctx := errgroup.WithContext(context.Background()) eg.SetLimit( 2 ) for i, url := range urls { i, url := i, url eg.Go( func () error { req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil ) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf( "%dçªç®ã®ãªã¯ãšã¹ã: %s \n " , i+ 1 , resp.Status) return nil }) } if err := eg.Wait(); err != nil { return err } return nil } äžã€åã®äŸããå€ãã£ãŠããç®æã¯äžç®æã§ãã ä»åã®äŸã§ã¯errgroup.Groupã®SetLimitã¡ãœãããåŒã³åºããŠããŸããåŒæ°ã§åæå®è¡æ°ãæž¡ããšãGoã¡ãœããå
ã§èµ·åããããŽã«ãŒãã³ã®æ°ãå¶éãããŸããä»åã®ãµã³ãã«ã³ãŒãã§ã¯ãŽã«ãŒãã³ã®åæå®è¡æ°ã2ãšããŠããŸãã errgroup.Groupã®å
éšã§ã¯ãããã¡ä»ããã£ãã«ãå©çšããŠããŽã«ãŒãã³ã®åæå®è¡æ°ãå¶åŸ¡ããŠããŸãã Goã¡ãœããå
éšã§ãŽã«ãŒãã³èµ·ååã«ãããã¡ä»ããã£ãã«ã«å€ã远å ããåŠçå®äºåŸã«ãã£ãã«ããå€ãåãåºããŠããŸãããããã¡ããã£ã±ãã«ãªã£ãå Žåã¯ãã®ã¿ã€ãã³ã°ã§åŠçã忢ããããã«ãªã£ãŠããŸãã ãã®ããã«SetLimitã¡ãœããã䜿ãã ãã§ãŽã«ãŒãã³ã®åæå®è¡æ°ãå¶åŸ¡ã§ããŸãã ãŸãšã ä»åã®èšäºã§ã¯ golang.org/x/sync/errgroup ããã±ãŒãžãå©çšãã䞊è¡åŠçã®å®è£
ã«ã€ããŠç޹ä»ããŸããã errgroupããã±ãŒãžãå©çšããããšã§ç°¡åã«äžŠè¡åŠçãèšè¿°ã§ããã ãã§ã¯ãªããšã©ãŒãã³ããªã³ã°ããã£ã³ã»ã«åŠçããŽã«ãŒãã³ã®åæå®è¡æ°å¶åŸ¡ãªã©äžŠè¡åŠçã®ãã¿ãŒã³ãå°å
¥ã§ããŸãã ã¿ãªããã䞊è¡åŠçãå®è£
ããéã«ã¯ãã² golang.org/x/sync/errgroup ããã±ãŒãžã®å©çšãæ€èšããŠã¿ãŠãã ããã ç§ãã¡ã¯äžç·ã«åããŠããã仲éãåéããŠããŸãïŒ åéè·çš®äžèЧ å·çïŒ @miyahara.hikaru ãã¬ãã¥ãŒïŒ @yamashita.tsuyoshi ïŒ Shodo ã§å·çãããŸãã ïŒ