ð èæ¯ç¥èïŒèªãã§ããåé¡ãžïŒ
ååã1ã€ã®channelã«å¯ŸããŠéä¿¡ã»åä¿¡ãè¡ãåºæ¬ãåŠã³ãŸããããããå®åã§ã¯ãè€æ°ã®channelã®ãã¡ãã©ãã1ã€ã§ãå€ãæ¥ãããããåŠçãããããšããå Žé¢ãé »ç¹ã«çºçããŸãããããå®çŸããã®ãselectæã§ãã
select {
case v := <-ch1:
// ch1ããåä¿¡ã§ããå Žå
case v := <-ch2:
// ch2ããåä¿¡ã§ããå Žå
default:
// ã©ã®caseãreadyã§ãªãå ŽåïŒå³åº§ã«å®è¡ãããïŒ
}
selectã¯switchã«äŒŒãæ§æã§ãããæ¯èŒå¯Ÿè±¡ã¯å€ã§ã¯ãªãchannelæäœã®å®è¡å¯åŠã§ããæåã®ã«ãŒã«ã¯æ¬¡ã®éãã§ãã
- è€æ°ã®caseã®ãã¡ãreadyãªïŒéåä¿¡å¯èœãªïŒãã®ãããã°ãããå®è¡ãã
- è€æ°ã®caseãåæã«readyãªå Žåã¯ãæ¬äŒŒã©ã³ãã ã«1ã€ãéžã°ããïŒå é ã«æžããcaseãåªå ãããããã§ã¯ãªãïŒ
- ã©ã®caseãreadyã§ãªãå Žåã
defaultãããã°å³åº§ã«defaultãå®è¡ïŒãããã¯ããªãïŒãdefaultããªããã°ãããããã®caseãreadyã«ãªããŸã§ãããã¯ãã
selectãç䟡ãçºæ®ããã®ããtime.Afterãšçµã¿åãããã¿ã€ã ã¢ãŠãåŠçã§ãã
select {
case v := <-ch:
fmt.Println("åä¿¡:", v)
case <-time.After(3 * time.Second):
fmt.Println("ã¿ã€ã ã¢ãŠã")
}
time.After(d)ã¯ãdçµéåŸã«çŸåšæå»ã1ã€éä¿¡ãã<-chan Timeãè¿ã颿°ã§ãããããselectã®caseã«å«ããããšã§ããchããå€ãæ¥ãã®ãåŸ
ã€ããäžå®æéåŸ
ã£ãŠãæ¥ãªããã°è«ŠããããšããåŠçãã远å ã®goroutineããã©ã°å€æ°ãªãã«ãã£ãæ°è¡ã§æžããŸããããã¯Goããã¿ã€ã ã¢ãŠãããšããé »åºãã¿ãŒã³ãèšèªããªããã£ãã¬ãã«ã§èªç¶ã«è¡šçŸã§ãã奜äŸã§ãã
ãã1ã€éèŠãªæ§è³ªãšããŠãselectã®caseæ°ã¯ã³ã³ãã€ã«æã«åºå®ããããšããå¶çŽããããŸããåçãªæ°ã®channelïŒäŸ:ã¹ã©ã€ã¹ã§æž¡ãããNåã®channelïŒãselectã§åæã«ç£èŠãããå Žåã¯ãæšæºã®selectæã§ã¯æžãããreflect.Selectãšããå¥ã®ä»çµã¿ãå¿
èŠã«ãªããŸãïŒããã¯Phase 3以éã§æ±ãå¿çšããŒãã§ãïŒãä»åã¯caseæ°ãåºå®ã®åºæ¬åœ¢ã«çµã£ãŠæ±ããŸãã
ð åé¡
以äžã®èŠä»¶ãæºããã1ã€ã®å®è¡å¯èœãªGoããã°ã©ã ãå®è£ ããŠãã ããã
firstResponse(ch1, ch2, ch3 <-chan string, timeout time.Duration) (string, error)ãšãã颿°ãå®è£ ããŠãã ããããã®é¢æ°ã¯selectæã§3ã€ã®channelãåæã«ç£èŠããæåã«ã©ãã1ã€ããå±ããå€ããã®ãŸãŸè¿ããŠãã ãããã©ã®channelãããå€ãæ¥ãªããŸãŸtimeoutãçµéããå Žåã¯ã空æååãšerrors.Newã§äœæãããšã©ãŒãè¿ããŠãã ããïŒtime.Afterã䜿ãããšïŒmainã§ã¯ãworker(label string, delay time.Duration) <-chan stringãšãããã«ããŒé¢æ°ïŒgoroutineãèµ·åããdelayåŸã«labelãéä¿¡ããchannelãè¿ãïŒãçšæããé å»¶ã®ç°ãªã3ã€ã®workerãäœã£ãŠfirstResponseãåŒã³åºããæéã§å¿çããworkerã®labelã衚瀺ãããããšã確èªããŠãã ãã- 3ã€ã®workerãã¹ãŠã
timeoutããé ãå¿çããã±ãŒã¹ãçšæããfirstResponseãæ£ããã¿ã€ã ã¢ãŠããšã©ãŒãè¿ãããšã確èªããŠãã ãã - åãããã°ã©ã å
ã«ã
selectã®defaultç¯ã䜿ã£ããã³ããããã³ã°ãªããŒãªã³ã°ã®äŸãå®è£ ããŠãã ããïŒäžå®åæ°ãçãééã§channelã確èªãããŸã å€ããªããã°ããŸã å±ããŠããŸããããšãã£ããã°ãåºããå€ãå±ãããããã§ããŒãªã³ã°ããããïŒ
go run main.goã§ãã®ãŸãŸå®è¡ã§ãããå®å
šãª1ã€ã®ããã°ã©ã ãšããŠæåºããŠãã ããã
ð ãã³ãïŒæ®µéçé瀺ïŒ
ãã³ã1 â æ¹åæ§
selectã¯è€æ°ã®caseã®ãã¡readyãªãã®ãå®è¡ããæ§æã§ãã3ã€ã®channelåä¿¡ãš1ã€ã®time.AfterãcaseãšããŠäžŠã¹ãã°ãã3ã€ã®ãã¡ã©ãããæ¥ããŸã§åŸ
ã€ããäžå®æéçµã£ãã諊ããããšããåŠçã1ã€ã®selectãããã¯ã ãã§è¡šçŸã§ããŸããdefaultç¯ãããselectã¯ãããã¯ããªãç¹ãdefaultç¯ããªãselectã¯å¿
ãã©ããã®caseãreadyã«ãªããŸã§ãããã¯ããç¹ã®éããæèããŠãã ããã
ãã³ã2 â ã¢ãããŒã
worker颿°ã¯åååŠãã goroutine+channelã®ãã¿ãŒã³ãã®ãã®ã§ãããããã¡1ã®channelã䜿ããšãåä¿¡åŽãåãåããªããŠãgoroutineãéä¿¡ã§ãããã¯ããããŸãŸæ®ãç¶ããå¿é
ãå°ãªããªããŸãïŒä»åã¯å¿
ãåä¿¡ããããtimeoutã§èŠæšãŠããã®ã©ã¡ããã§ãããgoroutineãªãŒã¯ãé¿ããèšèšãæèããçãã€ããŸãããïŒããã³ããããã³ã°ããŒãªã³ã°ã¯ãforã«ãŒãã®äžã«defaultç¯ã€ãã®selectã眮ããdefaultåŽã§time.Sleepãæãã§å°ãåŸ
ã£ãŠããæ¬¡ã®ããŒãªã³ã°ã«é²ã圢ã«ãªããŸãã
ãã³ã3 â ã³ãŒãéªšæ Œ
package main
import (
"errors"
"fmt"
"time"
)
func firstResponse(ch1, ch2, ch3 <-chan string, timeout time.Duration) (string, error) {
select {
case v := <-ch1:
return v, nil
case v := <-ch2:
return v, nil
case v := <-ch3:
return v, nil
case <-time.After(timeout):
return "", errors.New("timeout")
}
}
func worker(label string, delay time.Duration) <-chan string {
ch := make(chan string, 1)
go func() {
time.Sleep(delay)
ch <- label
}()
return ch
}
func main() {
ch1 := worker("worker-A", 300*time.Millisecond)
ch2 := worker("worker-B", 100*time.Millisecond)
ch3 := worker("worker-C", 500*time.Millisecond)
result, err := firstResponse(ch1, ch2, ch3, 1*time.Second)
fmt.Println(result, err)
// ãããããtimeoutã±ãŒã¹ãšdefaultç¯ã«ããããŒãªã³ã°ã远å ããŠãã
}
â æš¡ç¯è§£ç
package main
import (
"errors"
"fmt"
"time"
)
// firstResponse ã¯3ã€ã®channelãselectã§åæã«ç£èŠããæåã«å±ããå€ãè¿ãã
// ã©ã®channelãããå€ãæ¥ãªããŸãŸtimeoutãçµéããå Žåã¯ãšã©ãŒãè¿ãã
func firstResponse(ch1, ch2, ch3 <-chan string, timeout time.Duration) (string, error) {
select {
case v := <-ch1:
return v, nil
case v := <-ch2:
return v, nil
case v := <-ch3:
return v, nil
case <-time.After(timeout):
return "", errors.New("timeout: ã©ã®workerãããå¿çããããŸããã§ãã")
}
}
// worker ã¯æå®ããé
å»¶ã®åŸãlabelãchannelãžéä¿¡ããgoroutineãèµ·åãã
// åä¿¡å°çšchannelãè¿ãã
func worker(label string, delay time.Duration) <-chan string {
ch := make(chan string, 1)
go func() {
time.Sleep(delay)
ch <- label
}()
return ch
}
func main() {
fmt.Println("=== è€æ°workerã®ãã¡æéã®å¿çãåŸ
〠===")
ch1 := worker("worker-A", 300*time.Millisecond)
ch2 := worker("worker-B", 100*time.Millisecond)
ch3 := worker("worker-C", 500*time.Millisecond)
result, err := firstResponse(ch1, ch2, ch3, 1*time.Second)
if err != nil {
fmt.Println("ãšã©ãŒ:", err)
} else {
fmt.Println("æéã§å¿çããã®ã¯:", result)
}
fmt.Println("\n=== timeoutã±ãŒã¹ïŒå
šworkerãtimeoutããé
ãïŒ ===")
slowCh1 := worker("slow-A", 2*time.Second)
slowCh2 := worker("slow-B", 2*time.Second)
slowCh3 := worker("slow-C", 2*time.Second)
_, err = firstResponse(slowCh1, slowCh2, slowCh3, 300*time.Millisecond)
if err != nil {
fmt.Println("ãšã©ãŒ:", err)
}
fmt.Println("\n=== defaultç¯ã«ãããã³ããããã³ã°ã»ããŒãªã³ã° ===")
pollCh := worker("poll-result", 250*time.Millisecond)
received := false
for i := 0; i < 5 && !received; i++ {
select {
case v := <-pollCh:
fmt.Println("ããŒãªã³ã°äžã«åä¿¡:", v)
received = true
default:
fmt.Printf(" ãŸã å±ããŠããŸãããããŒãªã³ã°%dåç®\n", i+1)
time.Sleep(100 * time.Millisecond)
}
}
if !received {
fmt.Println(" ããŒãªã³ã°çµäºãŸã§ã«åä¿¡ã§ããŸããã§ãã")
}
}
â¶ å®è¡çµæãèŠãïŒgo run main.go / å®éã«æ€èšŒæžã¿ïŒ
=== è€æ°workerã®ãã¡æéã®å¿çãåŸ
〠===
æéã§å¿çããã®ã¯: worker-B
=== timeoutã±ãŒã¹ïŒå
šworkerãtimeoutããé
ãïŒ ===
ãšã©ãŒ: timeout: ã©ã®workerãããå¿çããããŸããã§ãã
=== defaultç¯ã«ãããã³ããããã³ã°ã»ããŒãªã³ã° ===
ãŸã å±ããŠããŸãããããŒãªã³ã°1åç®
ãŸã å±ããŠããŸãããããŒãªã³ã°2åç®
ãŸã å±ããŠããŸãããããŒãªã³ã°3åç®
ããŒãªã³ã°äžã«åä¿¡: poll-result
â» goroutineã®ã¹ã±ãžã¥ãŒãªã³ã°ã«ãã现ããã¿ã€ãã³ã°ã¯å®è¡ã®ãã³ã«å€ãããŸãããåŸåã¯æ¯ååãã§ãã
ðª Step-by-Step 解説
firstResponseå
ã®selectã¯ãch1ã»ch2ã»ch3ã®ãã¡æåã«readyã«ãªã£ãcaseãå®è¡ããŸããäžã®å®è¡äŸã§ã¯worker-Bã100msãworker-Aã300msãworker-Cã500msã§å¿çãããããch2ãæåã«readyã«ãªãããã®caseã®return v, nilãå®è¡ãããŠä»ã®caseã¯è©äŸ¡ãããã«é¢æ°ãæããŸãããŸã åä¿¡ãããªãã£ãch1ã»ch3ãžã®éä¿¡ã¯ããããã¡1ã®channelã®ãããã§goroutineåŽããããã¯ãããã«æžã¿ãéãã«çµäºããŸãïŒãããã¡ãªãã ã£ãå Žåãåãåãããªãã£ãéä¿¡åŽgoroutineã¯æ°žé ã«ãããã¯ããããŸãŸæ®ã£ãŠããŸããŸãïŒgoroutineãªãŒã¯ïŒã
2ã€ç®ã®åŒã³åºãã§ã¯ã3ã€ã®workerãã¹ãŠã2ç§åŸã«å¿çããã®ã«å¯Ÿã
timeoutã¯300msã§ããselectã®4ã€ç®ã®caseã§ããcase <-time.After(timeout)ãå
ã«readyã«ãªãããããã¡ããéžã°ããŠãšã©ãŒãè¿ããŸãããã€ã³ãã¯ãtime.Afterãè¿ã<-chan Timeãä»ã®channelãšãŸã£ããååã«æ±ããããã€ãŸããtimeoutãchannelçµç±ã®ã€ãã³ãã®1ã€ããšããŠçµ±äžçã«è¡šçŸãããããšã§ããä»ã®èšèªã®ããã«try/catch+ã¿ã€ããŒå²ã蟌ã¿ã®ãããªç¹å¥ãªä»çµã¿ã¯äžèŠã§ãã
select {
case v := <-pollCh:
received = true
default:
time.Sleep(100 * time.Millisecond)
}
defaultç¯ãããããããã®selectã¯pollChã«ãŸã å€ããªããŠã絶察ã«ãããã¯ããŸãããreadyã§ãªããã°å³åº§ã«defaultã«èœã¡ãŠtime.Sleepã§å°ãåŸ
ã¡ã次ã®ã«ãŒããžé²ã¿ãŸããããã¯ãå€ãããã°åŠçãããªããã°ä»ã®ä»äºãããïŒãŸãã¯å°ãåŸ
ã£ãŠå詊è¡ããïŒããšãããããããã³ã°ãé¿ãããã·ãã¥ãšãŒã·ã§ã³ã§äœ¿ãããå®ç³ãã¿ãŒã³ã§ããä»åã¯250msåŸã«å±ãå€ã100msééã§ããŒãªã³ã°ããŠããããã3åã®ããŸã å±ããŠããŸãããã®åŸã4åç®ã®ã«ãŒãã§åä¿¡ã«æåããŸãã
ð¡ èšèšææ³ã»ãªãããæžãã®ã
selectãGoã«èšèªçµã¿èŸŒã¿ã§çšæãããŠããã®ã¯ãGoã䞊è¡åŠçã«ãããŠãè€æ°ã®å
¥åæºãåæã«åŸ
ã€ããšããæäœãäžçŽåžæ°ãšããŠæ±ã£ãŠããããã§ããä»ã®èšèªã§ã¯ãè€æ°ã®éåæåŠçã®ãã¡ã©ããã®å®äºãåŸ
ã€ã«ã¯ãFutureãPromiseãã©ããããå°çšAPIããã³ãŒã«ããã¯ã®åæãå¿
èŠã«ãªããã¡ã§ããGoã§ã¯selectãšãã1ã€ã®æ§æããchannelãšããçµ±äžãããã€ã³ã¿ãŒãã§ãŒã¹ã®äžã§ãã¿ã€ã ã¢ãŠãããè€æ°ãœãŒã¹ã®å€éåãããã³ããããã³ã°ãªç¢ºèªãã®ãã¹ãŠã衚çŸã§ããŸããtime.Afterãselectã®caseã«ãã®ãŸãŸäžŠã¹ãããèšèšã¯è±¡åŸŽçã§ãããäžå®æéåŸ
ã€ããšããæéã®çµéããGoã§ã¯ç¹å¥æ±ãããchannelããå€ãå±ããšãã1ã€ã®ã€ãã³ããšããŠçµ±äžçã«ã¢ãã«åãããŠããŸãããã®äžè²«æ§ããGoã®äžŠè¡åŠçã³ãŒããä»èšèªã«æ¯ã¹ãŠçãèªã¿ããããªã倧ããªçç±ã®1ã€ã§ããäžæ¹ã§ãselectã®caseæ°ãã³ã³ãã€ã«æã«åºå®ããããšããå¶çŽã¯ãGoããå®è¡æã®æè»æ§ããããã³ã³ãã€ã«æã®æå¿«ãã»åå®å
šæ§ããåªå
ããèšèšææ³ã®è¡šãã§ããããŸããð ä»èšèªãšã®æ¯èŒ
| èŠ³ç¹ | Go | Java | Python | JavaScript/Node.js |
|---|---|---|---|---|
| çžåœããä»çµã¿ | selectæïŒèšèªçµã¿èŸŒã¿æ§æïŒ | CompletableFuture.anyOf(...) | asyncio.wait(..., return_when=FIRST_COMPLETED) | Promise.race([...]) |
| ã¿ã€ã ã¢ãŠãä»ãåŸ æ© | selectã®caseã«time.Afterã䞊ã¹ã | future.get(timeout, unit) | asyncio.wait_for(coro, timeout) | Promise.race([p, timeoutPromise])ïŒtimeoutçšPromiseãèªåã§çšæïŒ |
| è€æ°readyãªå Žåã®éžã°ãæ¹ | æ¬äŒŒã©ã³ãã ã«1ã€éžæïŒå ¬å¹³æ§ãä¿èšŒïŒ | å®è£ äŸåïŒéåžžã¯æåã«å®äºãããã®ïŒ | å®äºããå šã¿ã¹ã¯ã®éåãè¿ã | æåã«resolve/rejectãããã®ãå〠|
| ãã³ããããã³ã°ç¢ºèª | defaultç¯ã€ãselect | future.isDone()ãªã©ã®ããŒãªã³ã°ã¡ãœãã | task.done()ãªã©ã®ããŒãªã³ã°ã¡ãœãã | åºæ¬çã«ã³ãŒã«ããã¯/ã€ãã³ãé§åã§ãåæçãªãã³ããããã³ã°ç¢ºèªãšããæŠå¿µèªäœãèã |
Promise.raceãCompletableFuture.anyOfã䜿ã£ãããšããã人ã«ãšã£ãŠã¯ããè€æ°ã®éåæåŠçã®ãã¡æéã®ãã®ãåŸ
ã€ããšããçºæ³èªäœã¯éŠŽæã¿ãããã¯ãã§ããéãã¯ãGoã§ã¯time.Afterã«ããã¿ã€ã ã¢ãŠããåãselectæ§æã®äžã«å¯ŸçãªcaseãšããŠæžããç¹ã§ãä»èšèªã®ããã«ãæ¬äœã®åŠçããšãã¿ã€ã ã¢ãŠãçšã®ç¹å¥ãªAPIããå¥ã
ã«çµã¿åãããå¿
èŠããããŸããã
ð å®åã§ã®äœ¿ãã©ãã
- å€éšAPIãžã®ã¿ã€ã ã¢ãŠãä»ããªã¯ãšã¹ã: è€æ°ã®ã¬ããªã«/ãã©ãŒã«ããã¯å
ã«åæã«ãªã¯ãšã¹ããéããæåã«è¿ã£ãŠããå¿çãæ¡çšãã€ã€ãäžå®æéã§è«Šãããã¿ãŒã³ïŒä»åã®
firstResponseã¯ãã®ç°¡æçïŒ - graceful shutdown: ã¡ã€ã³ã®åŠçchannelãšãOSã·ã°ãã«ãåãåãchannelïŒ
os.SignalïŒãselectã§åæã«ç£èŠããã·ã°ãã«ãåãããå®å šã«çµäºåŠçãžç§»ãå®è£ ã¯æ¬çªãµãŒãã¹ã®å®çªãã¿ãŒã³ - ãµãŒããããã¬ãŒã«ãŒããªãã©ã€æ©æ§:
context.Done()ïŒå éšçã«ã¯channelïŒãšåŠçå®äºchannelãselectã§ç£èŠãããã£ã³ã»ã«ãšæ£åžžå®äºã®ã©ã¡ããå ã«èµ·ãããã§åå²ãã - ã€ãã³ãã«ãŒãçãªå€éå: è€æ°ã®ã€ãã³ããœãŒã¹ïŒã¿ã€ããŒãå€éšããã®éç¥ãå
éšãã¥ãŒïŒã1ã€ã®
selectã«ãŒãã§æããåçŽã ãå ç¢ãªãã£ã¹ãããã£ã®å®è£
â ïž ãããã誀解ã»ãã¹
| 誀解ã»ãã¹ | ãªãèµ·ããã | æ£ããçè§£ |
|---|---|---|
| selectã¯å é ã«æžããcaseãåªå ããããšæã蟌ã | switchãif-else ifã®ãããªãäžããé ã«è©äŸ¡ããšããæèŠãæã¡èŸŒã | è€æ°ã®caseãåæã«readyãªå ŽåãGoã¯æ¬äŒŒã©ã³ãã ã«1ã€ãéžã¶ãç¹å®ã®caseãåªå
ãããå Žåã¯ãã¹ãããselectãªã©å¥ã®èšèšãå¿
èŠ |
| defaultç¯ããªãselectããã³ããããã³ã°ã ãšæã蟌ã | selectãšããååãdefaultã®ååšãæ··åãã | defaultç¯ãããå Žåã®ã¿ãã³ããããã³ã°ã«ãªããdefaultããªãselectã¯ãããããã®caseãreadyã«ãªããŸã§éåžžã®channelæäœãšåæ§ã«ãããã¯ãã |
| ã«ãŒãã®äžã§æ¯åtime.AfterãåŒãã§ããŸã | ã¿ã€ã ã¢ãŠãåŠçã颿°åããéã宿ã«forã«ãŒãã®å
åŽã«time.AfterãæžããŠããŸã | time.Afterã¯ã«ãŒãã®ãã³ã«æ°ããã¿ã€ããŒãçæããçºç«ãããŸã§GCãããªããã«ãŒãå
ã§ç¹°ãè¿ã䜿ãå Žåã¯time.NewTimerã1åã ãçæããStop()ããªã»ããã§äœ¿ãåãèšèšãæšå¥šããã |
| ãããã¡ãªãchannelãworkerã«äœ¿ããåä¿¡ãããªãã£ãå Žåã«goroutineããããã¯ããç¶ããããšã«æ°ã¥ããªã | ãéä¿¡ãããããã§çµããããšããæèŠã§å®è£ ããŠããŸã | selectã§è€æ°channelã®ãã¡1ã€ããåä¿¡ããªãèšèšã§ã¯ãåãåãããªãéä¿¡åŽgoroutineãæ®ãå¯èœæ§ãããããããã¡ä»ãchannelã«ãããcontextã§ãã£ã³ã»ã«ãäŒããçã®å¯Ÿçãå¿
èŠ |
ð æ¬¡ã®ã¹ããã
- çºå±:
firstResponseã®timeoutãtime.Afterã§ã¯ãªãcontext.WithTimeoutã§å®è£ ãçŽããŠã¿ãŸããããctx.Done()ãselectã®caseã«äœ¿ãæžãæ¹ã¯ãPhase 2ã®æ¬¡ã®ããŒããcontext ããã±ãŒãžããžã®æ©æž¡ãã«ãªããŸã - 次åäºå: Day 012 â sync.WaitGroupïŒå®è£ åé¡ïŒãè€æ°ã®goroutineã®å®äºããchannelã䜿ããã«åŸ ã¡åãããæšæºçãªæ¹æ³ãåŠã³ãŸã