본문 바로가기
golang

Go context 파헤치기

by marble25 2023. 9. 29.

go 언어에는 다른 언어에는 없는 context라는 개념이 존재한다. context라는 생소한 개념을 한번 정리해보고자 한다.

참고 문서

https://pkg.go.dev/context

https://go.dev/blog/context

What is context?

작업을 지시할 때 deadline, cancel 시그널, 또는 request-scope의 변수들을 전송할 수 있는 객체이다.

모든 Context는 root로 Background Context를 가지는 tree 구조로 되어 있다. 부모 Context의 속성을 물려받아 새로운 Context를 생성할 수 있다.

Context는 struct type 내에 저장되면 안된다. 대신, 명시적으로 필요로하는 function에 context를 넘겨주어야 한다. Context는 첫 번째 parameter로 들어가야 한다.

func DoSomething(ctx context.Context, arg Arg) error {
	// ... use ctx ...
}

Context value는 request-scoped data를 위해 사용되어야 하지, function의 optional parameter를 위해서 사용되어서는 안된다.

Context interface는 다음과 같이 구성되어 있다.

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
  // Done이 closed되지 않았으면 Err()는 nil을 리턴한다.
  // Done이 close되었다면, Canceled 또는 DeadlineExceeded를 의미하는 에러를 리턴한다.
	Err() error
  // Context에 실을 key-value를 의미한다.
	Value(key any) any
}

How to use context

Context.Background()

Non-nil, empty Context를 생성해준다. Cancel되지 않고, value 없고, 데드라인도 없다. 대부분 main function이나, initialization, test 등 top-level에서 사용된다.

Context.TODO()

Background와 거의 유사하게, non-nil, empty context를 생성해준다. 어떤 context를 사용할지 모르겠거나, 아직 available하지 않은 경우에 사용된다.

Context.WithValue(parent Context, key, val any)

Parent context의 copy에 key, val을 세팅해서 전달한다.

제공된 key는 collision을 방지하기 위해 string과 같은 built-in type이면 안된다. Context key는 concrete type인 struct{} 가 주로 사용된다.

type stateKey struct {
}

ctx := context.WithValue(req.Context(), stateKey{}, "value")
val := ctx.Value(stateKey{}).(string)

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

새 Done 채널과 함께 parent의 copy를 리턴한다. 새 context의 done은 cancel function이 호출되거나 parent done 채널이 close되었을 때 close된다.

package main

import (
  "context"
  "fmt"
  "sync"
  "time"
)

var wg sync.WaitGroup

func main() {
  wg.Add(1)
  ctx, cancel := context.WithCancel(context.Background())

  go PrintTick(ctx)

  time.Sleep(5 * time.Second)
  cancel()

  wg.Wait()
}

func PrintTick(ctx context.Context) {
  tick := time.Tick(time.Second)
  for {
    select {
    case <-ctx.Done():
      fmt.Println("Done:", ctx.Err())
      wg.Done() // 고루틴이 종료되었음을 알림
      return
    case <-tick:
      fmt.Println("tick")
    }
  }
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

새 Done 채널과 함께 parent의 copy를 리턴한다. 부모의 deadline이 d보다 작으면, WithDeadline은 parent와 동일한 context를 가지게 된다. 새 context의 done 채널은 deadline이 지나거나, cancel function이 호출되거나, 부모의 done 채널이 닫히면 close된다.

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithDeadline(parent, time.Now().Add(timeout)) 을 리턴한다. 현재 시간으로부터 특정 시간 후에 context가 만료된다.

'golang' 카테고리의 다른 글

golang에서 for loop scoping  (1) 2023.10.02
Go 상속 vs 구성  (1) 2023.10.01
goroutine 파헤치기  (0) 2023.09.23
Formatting in golang  (0) 2023.09.03
Internal package in golang  (0) 2023.09.03