이번 게시글에서는 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' 카테고리의 다른 글
[golang] go context의 활용2(go context사용법 및 예제2) (0) | 2021.12.09 |
---|---|
[golang] go context의 활용1(go context사용법 및 예제1) (0) | 2021.12.07 |
[golang] golang 채널(channel) 사용법, 사용예제 (0) | 2021.12.06 |
[golang] 고루틴(go routine)이란 (0) | 2021.11.15 |
[golang] golang defer 사용법 및 예제(golang의 catch는 defer안에서) (0) | 2021.11.15 |
댓글