golang 기초 - 컨텍스트

한나리·2025년 1월 16일

Go

목록 보기
15/19
post-thumbnail

채널과 컨텍스트

채널(Channel)과 컨택스트(Context)는 GO 언어에서 동시성 프로그래밍을 도와주는 기능

  • 채널
    채널은 고루틴 간 메시지를 전달하는 메시지 큐
    채널을 사용하면 뮤텍스 없이 동시성 프로그래밍이 가능
  • 컨텍스트
    컨텍스트는 고루틴에 작업을 요청할 때 작업 취소나 작업 시간 등을 설정할 수 있는 작업 명세서 역할

채널과 컨텍스트를 사용해 특정 데이터를 전달하거나 특정 시간 동안만 작업을 요청하거나 작업 도중에 작업 취소를 요청할 수 있음

컨텍스트

컨텍스트는 context 패키지에서 제공하는 기능으로 작업을 지시할 때 작업 가능 시간, 작업 취소 등의 조건을 지시할 수 있는 작업 명세서 역할을 함. 새로운 고루틴으로 작업을 시작할 때 일정 시간 동안만 작업을 지시하거나 외부에서 작업을 취소할 때 사용.
또한 작업 설정에 관한 데이터를 전달할 수도 있음

작업 취소가 가능한 컨텍스트

작업 취소 기능을 가진 컨텍스트. 이 컨텍스트를 만들어서 작업자에게 전달하면 작업을 지시한 지시자가 원할 때 작업 취소를 알릴 수 있음

context.WithCancel의 주요 구조체 cancelCtx

type cancelCtx struct {
    Context        // 부모 컨텍스트
    done chan struct{} // 취소 신호를 전달하기 위한 채널
    mu   sync.Mutex    // 동시성 제어를 위한 뮤텍스
    err  error         // 취소 시 전달할 에러
}

done: 컨텍스트 취소 신호를 전달하는 데 사용되는 채널.
mu: cancel 호출 시 데이터 경쟁을 방지.
err: context.Canceled 에러를 전달.

cancel() 호출 이후의 동작

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)이 핵심 역할을 합니다.

  • 작업이 취소될 때까지 1초마다 메시지를 출력하는 고루틴 예제
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
  • 취소가 가능한 컨텍스트를 생성. context.WithCancel() 함수로 취소 가능한 컨텍스트를 생성. 상위 컨텍스트를 인수로 넣으면 그 컨텍스트를 감싼 새로운 컨텍스트를 생성. 상위 컨텍스트가 없다면 가장 기본적인 컨텍스트인 context.Background()를 넣어줌. context.WithCancel() 함수는 값을 두 개를 반환하는데 첫 번째가 컨텍스트 객체이고 두 번째가 취소 함수. 두 번째 취소 함수를 사용해서 원할 때 취소할 수 있음
  • main() 함수에서 5초 이후에 취소 함수를 호출해 작업 취소를 알림. 그러면 컨텍스트는 Done()채널에 시그널을 보내 작업자가 작업 취소를 할 수 있도록 알림.
  • PrintEverySecond() 루틴에서 인수로 받은 컨텍스트의 Done() 채널의 시그널이 있는지를 검사. 컨텍스트가 완료될 때 Done() 채널에 시그널을 넣기 때문에 여기서 메시지를 수신하면 고루틴을 종료

작업 시간을 설정한 컨텍스트

앞 예제의 컨텍스트를 만드는 부분을 다음과 같이 변경하면 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 패키지의 WithTimeout() 함수를 사용해서 작업 시간을 설정.
    두 번째 인수로 시간을 설정하면 그 시간이 지난 뒤 컨텍스트의 Done() 채널에 시그널을 보내서 작업 종료를 요청. WithTimeout() 참수 역시 두 번째 반환값으로 cancel 함수를 반환하기 때문에 작업 시간 전에 원하면 언제든지 작업 취소를 요청할 수 있음

특정 값을 설정한 컨텍스트

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
  • number를 키로 값을 9로 설정한 컨텍스트를 만듦. 이렇게 만든 컨텍스트를 square() 함수 인수로 넘겨서 값을 사용하게 함.
  • 컨텍스트 ctx의 Value() 메서드로 값을 읽어옴. Value() 메서드의 반환 타입은 빈 인터페이스. int 타입으로 변환하여 사용

컨텍스트에 값을 설정해서 다른 고루틴으로 작업을 지시할 때 외부 지시사항으로 설정할 수 있음. 이때 지시자와 작업자 사이에 어떤 키로 어떤 값이 들어올지에 대한 약속이 필요.

‼️그럼 취소도 되면서 값도 설정하는 컨텍스트는?

컨텍스트를 만들 때 항상 상위 컨텍스트 객체를 인수로 넣어줘야 하는데, 일반적으로 context.Background()를 넣어줬음. 여기에 이미 만들어진 컨텍스트 객체를 넣어줘도 됨. 이를 통해서 여러 값을 설정하거나 기능을 설정할 수 있음

ctx, cancel := context.WithCancel(context.Background()) ①
ctx = context.WithValue(ctx, "number", 9) ②
ctx = context.WithValue(ctx, "keyword", "Lilly")

① 먼저 취소 기능이 있는 컨텍스트를 생성. 이것을 다시 감싸서 ② 값을 설정한 컨텍스트를 생성. ③ 컨텍스트를 여러 번 감싸서 여러 값을 설정할 수 있음

profile
내가 떠나기 전까지는 망하지 마라, 블록체인 개발자

0개의 댓글