채널(Channel)과 컨택스트(Context)는 GO 언어에서 동시성 프로그래밍을 도와주는 기능
채널과 컨텍스트를 사용해 특정 데이터를 전달하거나 특정 시간 동안만 작업을 요청하거나 작업 도중에 작업 취소를 요청할 수 있음
컨텍스트는 context 패키지에서 제공하는 기능으로 작업을 지시할 때 작업 가능 시간, 작업 취소 등의 조건을 지시할 수 있는 작업 명세서 역할을 함. 새로운 고루틴으로 작업을 시작할 때 일정 시간 동안만 작업을 지시하거나 외부에서 작업을 취소할 때 사용.
또한 작업 설정에 관한 데이터를 전달할 수도 있음
작업 취소 기능을 가진 컨텍스트. 이 컨텍스트를 만들어서 작업자에게 전달하면 작업을 지시한 지시자가 원할 때 작업 취소를 알릴 수 있음
cancelCtxtype cancelCtx struct {
Context // 부모 컨텍스트
done chan struct{} // 취소 신호를 전달하기 위한 채널
mu sync.Mutex // 동시성 제어를 위한 뮤텍스
err error // 취소 시 전달할 에러
}
done: 컨텍스트 취소 신호를 전달하는 데 사용되는 채널.
mu: cancel 호출 시 데이터 경쟁을 방지.
err: context.Canceled 에러를 전달.
1️⃣ Done 채널이 닫힘
: Done 채널을 select에서 대기 중이던 모든 Goroutine이 깨어남.
select {
case <-ctx.Done():
fmt.Println("Context canceled")
return
}
2️⃣ 에러 확인 가능
: ctx.Err()를 호출하여 context.Canceled 에러를 확인할 수 있음
3️⃣ 자원 정리
: cancel은 하위 컨텍스트도 취소하며, 이를 통해 자원을 정리하고 Goroutine이 적절히 종료되도록 보장
cancel()은 Done 채널을 닫아 취소 신호를 보냅니다.
컨텍스트의 취소는 재귀적으로 하위 컨텍스트에 전달됩니다.
Done 채널이 닫히면 모든 대기 중인 Goroutine이 작업을 중단하거나 취소 작업을 처리합니다.
내부적으로 cancelCtx 구조체와 채널(done)이 핵심 역할을 합니다.
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx, cancel := context.WithCancel(context.Background()) //컨텍스트 생성
go PrintEverySecond(ctx)
time.Sleep(5 * time.Second)
cancel() //취소
wg.Wait()
}
func PrintEverySecond(ctx context.Context) {
tick := time.Tick(time.Second)
for {
select {
case <-ctx.Done(): //취소 확인
wg.Done()
return
case <-tick:
fmt.Println("Tick")
}
}
}
//결과
Tick
Tick
Tick
Tick
Tick
앞 예제의 컨텍스트를 만드는 부분을 다음과 같이 변경하면 3초 뒤 종료되는 컨텍스트를 만들 수 있음
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) //컨텍스트 생성
go PrintEverySecond(ctx)
time.Sleep(5 * time.Second)
cancel() //취소
wg.Wait()
}
func PrintEverySecond(ctx context.Context) {
tick := time.Tick(time.Second)
for {
select {
case <-ctx.Done(): //취소 확인
wg.Done()
return
case <-tick:
fmt.Println("Tick")
}
}
}
//결과
Tick
Tick
Tick (2초 후 종료)
context.WithValue() 함수를 이용해서 컨텍스트에 특정 키로 값을 읽어올 수 있도록 설정할 수 있음.
package main
import (
"context"
"fmt"
"sync"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
ctx := context.WithValue(context.Background(), "number", 9)
go square5(ctx)
wg.Wait()
}
func square5(ctx context.Context) {
if v := ctx.Value("number"); v != nil { //컨텍스트에서 값 읽기
n := v.(int)
if n == 1 {
fmt.Printf("Square 1*1: %d\n", n*n)
} else {
fmt.Printf("Square: %d\n", n*n)
}
}
wg.Done()
}
//결과 : 9일 때
Square: 81
//결과 : 1일 때
Square 1*1: 1
컨텍스트에 값을 설정해서 다른 고루틴으로 작업을 지시할 때 외부 지시사항으로 설정할 수 있음. 이때 지시자와 작업자 사이에 어떤 키로 어떤 값이 들어올지에 대한 약속이 필요.
컨텍스트를 만들 때 항상 상위 컨텍스트 객체를 인수로 넣어줘야 하는데, 일반적으로 context.Background()를 넣어줬음. 여기에 이미 만들어진 컨텍스트 객체를 넣어줘도 됨. 이를 통해서 여러 값을 설정하거나 기능을 설정할 수 있음
ctx, cancel := context.WithCancel(context.Background()) ①
ctx = context.WithValue(ctx, "number", 9) ②
ctx = context.WithValue(ctx, "keyword", "Lilly") ③
① 먼저 취소 기능이 있는 컨텍스트를 생성. 이것을 다시 감싸서 ② 값을 설정한 컨텍스트를 생성. ③ 컨텍스트를 여러 번 감싸서 여러 값을 설정할 수 있음