본문 바로가기
golang

[golang] go context란

by devjh 2021. 12. 6.
반응형

이번 게시글에서는 golang의 context에 대해 정리합니다.

1. context란

운영체제를 공부할때 들었던 컨텍스트 스위칭(문맥교환으로 외웠던)에 사용된 용어 입니다.

고랭의 context도 비슷합니다.

conext패키지는 고루틴의 문맥관리(상태나 흐름을 관리해주기 위한)를 위한 패키지입니다.

고루틴 로직에 결함이 생겨 끝나지 않는 무한루프가 생기는걸 방지하거나, 진행되는 문맥을 강제로 종료시키거나, 해당 문맥이 더이상 유효하지 않을때 불필요한 로직을 진행시키지 않게 하는 등 문맥을 관리하는데 사용됩니다.

 

2. context 인터페이스

type Context interface {
   Deadline() (deadline time.Time, ok bool)

   Done() <-chan struct{}

   Err() error

   Value(key interface{}) interface{}
}

context는 크게 네개의 기능이 있습니다.

  • (1). Done()은 문맥 종료시 호출되는 수신 채널
  • (2). Deadline()은 timeout을 통해 Cancel을 호출하여 Done()을 호출하는 기능
  • (3). Value()는 컨텍스트에서 사용할 key value를 저장하는 기능
  • (4). Err()는 error를 리턴해주는 용도

 

해당 메서드를 호출하기 위해 래핑된 메서드들이 많지만 가장 큰 틀은 네가지입니다.

네가지 기능을 하나씩 예제를 통해 확인해보겠습니다.

 

3. context 생성하기

package main

import "context"

func main() {
	ctx := context.Background()
}

context.Background() 혹은 context.TODO()를 통해 생성합니다.

 

4. key, value를 포함시킨 context 생성하기

package main

import (
	"context"
	"fmt"
)

func main() {
	ctx := context.WithValue(context.Background(), "key", "value")
	fmt.Println(ctx.Value("key"))
}

ctx.WithValue로 해당 문맥에서의 key value를 저장해주고,

ctx.Value(key)로 해당 문맥에서의 value를 가져올 수 있습니다.

(3). Value()는 컨텍스트에서 사용할 key value를 저장하는 기능 

은 WithValue로 구현할 수 있습니다.

 

5. 원하는 조건에 cancel()을 호출하여 문맥을 관리하기

package main

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

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	result, err := doSomethingWithCtx(ctx, cancel)
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
}

func doSomethingWithCtx(ctx context.Context, cancel context.CancelFunc) (string, error) {

	// 단순히 타임아웃을 설정한것처럼 만들었지만
	// 특정 조건이 되었을때 cancel()을 호출해서 해당 문맥을 안전하게 종료
	// cancel()이 호출되면 ctx.Done() 채널이 수신
	go func() {
		select {
		case <-ctx.Done():
			cancel()
		default:

		}
		time.Sleep(time.Second * 1)
		cancel()
	}()

	// 비즈니스 로직을 실행시키는 고루틴
	// select를 사용해 cancel()의 호출여부를 확인하고
	// 문맥이 종료되었다면 로직을 진행시키지 않습니다.
	ch := make(chan string)
	go func(ch chan string) {
		select {
		case <-ctx.Done():
			return
		default:

		}
		time.Sleep(time.Second * 2)
		ch <- "hello world"
	}(ch)

	// 비즈니스 로직이 수행되서 나온 결과인지
	// ctx.Done()이 호출된 결과인지를 select 를 사용해 확인하고 return 해줍니다
	select {
	case result := <-ch:
		return result, nil
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

context.WithCancel로 context를 생성하면 cancel 메서드를 직접 제어할 수 있습니다.

예제에서는 cancel()을 활용하여 timeout을 1초로 주고 문맥을 종료시켰지만

원하는 조건에 cancel()메서드를 호출하여 문맥을 안전하게 종료시킬 수 있습니다.

(1). Done()은 문맥 종료시 호출되는 수신 채널
(4). Err()는 error를 리턴해주는 용도

 

cancel()을 호출하면 Done()채널에 메시지가 수신되며 Err()가 채워집니다.

각각의 고루틴들은 cancel()을 확인하고 해당문맥이 종료되었다면 불필요한 작업을 수행하지 않습니다.

 

6. WithDeadline()으로 timeout 걸기

package main

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

func main() {
   timeOutDuration := time.Time{}.Add(time.Second * 2)
   ctx, cancel := context.WithDeadline(context.Background(), timeOutDuration)
   result, err := doSomethingWithCtx(ctx, cancel)
   if err != nil {
      panic(err)
   }
   fmt.Println(result)
}

func doSomethingWithCtx(ctx context.Context, cancel context.CancelFunc) (string, error) {
   go func() {
      // context.WithDeadline()으로 문맥을 생성했으므로 타임아웃을 통한 cancel()호출은 하지 않습니다.
      for {
         select {
         case <-ctx.Done():
            cancel()
         default:

         }
         if ~~~ {
            cancel()
         }
         time.Sleep(time.Second * 1)
      }
   }()

   // 비즈니스 로직을 실행시키는 고루틴
   ch := make(chan string)
   go func(ch chan string) {
      select {
      case <-ctx.Done():
         cancel()
      default:

      }
      time.Sleep(time.Second * 2)
      ch <- "hello world"
   }(ch)

   // 비즈니스 로직이 수행되서 나온 결과인지
   // ctx.Done()이 호출된 결과인지를 select 를 사용해 확인해줍니다.
   select {
   case result := <-ch:
      return result, nil
   case <-ctx.Done():
      return "", ctx.Err()
   }
}

context를 생성할때 WithDeadline옵션을 줘서 생성하면 timeout을 통한 cancel호출 과정이 context내부에 예약됩니다.

(2). Deadline()은 Cancel 을 호출하여 Done을 호출하는 기능

Deadline()을 호출하면 내부적으로 Cancel을 호출하고 Done채널에 송신해주고, Error에 Candel된 이유를 채워줍니다.

WithDeadline과 비슷한 WithTimeOut도 있습니다.

WithDealine은 끝나는 시각을 정해줘야하고, WithTimeOut은 얼마후에 cancel을 시킬지 정하는 차이입니다.

(WithTimeOut은 Duration을 받아서 time.Now().Add(Duration)를 해서 WithDeadline 함수를 콜하는 context 내장 함수)

 

아래 게시글은 실제로 context를 활용하는 예제입니다.

 

[golang] go context의 활용(go context사용법 및 예제1)

이번 게시글에서는 go context를 활용하는 방법을 정리 합니다. 1. go context란 golang을 사용하다보면, 메서드를 호출할때 context를 아규먼트로 요구하는 경우가 종종 있습니다. context를 아규먼트로 넘

frozenpond.tistory.com

 

반응형

댓글