학습일지/Language
Go - Context 정리
inspirit941
2022. 5. 26. 01:16
반응형
Context Package
- go의 다양한 패키지에서 사용되고 있는 Context
Context의 기능은 크게 세 가지.
- Deadlines
- Concellation Signals
- Request-scoped values
Deadline - withTimeout, withDeadline
- WithDeadline: 시작 시간과 끝 시간을 정하고, 그 시간동안만 실행되도록 (endtime)
- WithTimeout: 시작 시간을 정하고, 얼마의 시간이 지난 뒤 종료되도록. (Duration)
const shortDuration = 1 * time.Millisecond
func main() {
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
defer cancel()
select {
case <- time.After(1 * time.Second):
fmt.Println("OverSlept")
case <- ctx.Done():
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
}
}
Cancellation
func main() {
ch := make(chan struct{})
run := func(ctx context.Context) {
n := 1
for {
select {
case <- ctx.Done(): // 2. ctx is canceled. close ch
fmt.Println("exiting")
close(ch)
return
default:
time.Sleep(time.Millisecond * 300)
fmt.Println(n)
n ++
}
}
}
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Second * 2)
fmt.Println("goodbye")
cancel() // 1. cancel ctx
}()
go run(ctx)
fmt.Println("waiting to cancel ...")
<- ch // 3. ch is closed, exit. (graceful shutdown)
fmt.Println("bye")
}
Request-scope Values
type jwt string
const auth jwt = "JWT"
func main() {
ctx := context.WithValue(context.Background(), auth, "Bearer hi") // key - value 형태로 context에 저장.
bearer := ctx.Value(auth)
str, ok := bearer.(string) // context의 value 가져와서 타입 캐스팅.
if !ok {
log.Fatalln("not a string")
}
fmt.Println("values: " , str)
}
Best Practise
- context는 first argument로 선언한다. microservice의 경우 대부분의 request에서 context가 첫번째 argument로 들어가 있음
- memory leak 방지를 위해 cancel function이 제대로 호출되는지 확인.
- WithValue를 너무 많이 쓰는 것은 지양한다.
에서 제공한 예시코드.
package main
import (
"math/rand"
"time"
"fmt"
)
const (
minLatency = 10
maxLatency = 5000
timeout = 3000
)
func main() {
// flight routes를 찾는 프로그램
// mock backend / db 사용 예정
// context를 사용해서 cancel 로직을 서로 다른 goroutine process에 propagate signaling하는 것이 목표
// 1. 예컨대 응답이 3초 넘어가면 cancel하도록 만들고 싶다면
rootCtx := context.Background()
ctx, cancel = context.WithTimeout(rootCtx, time.Duration(timeout) * time.Millisecond)
defer cancel()
// 2. 터미널에서 Interrupt할 경우 context cancel 실행
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
go func() {
<-sig
fmt.Println("aborting due to interrupt")
cancel()
exit(0)
}
fmt.Println("starting to search from nyc - london...")
res, err := Search(ctx, "nyc", "london")
if err != nil {
fmt.Println(err)
}
fmt.Println("got result", res)
}
func Search(ctx context.Context, from, to string) ([]string, error) {
// slowSearch
// ctx.Done() 이 close되었는지 확인
res := make(chan []string)
go func() {
result, err := slowSearch(from, to)
res <- result
close(res)
}()
// wait for 2 events: 응답값은 둘 중 하나다. timeout이거나 리턴값이거나.
for {
select {
case dst := <-res:
return dst, nil
case <- ctx.Done():
return []string{}, ctx.Err()
}
}
return []string{}, nil
}
// search는 굉장히 느린 함수로, 문자열 slice를 리턴한다고 가정한다.
func slowSearch(from, to string) ([]string, error) {
// sleep for a random period btwn min / max latency
rand.Seed(time.Now().Unix())
latency := rand.Intn(maxLatency - minLatency + 1) - minLatency
fmt.Println(latency)
time.Sleep(time.Duration(latency) * time.Millisecond)
return []string{from+"-"+to+"-british airways-11am", from+"-"+to+"-delta airlines-12am"}, nil
}
반응형