728x90
반응형

Graceful Shutdown

  • 취소 가능한 컨텍스트 생성
    • 컨텍스트와 취소 함수가 리턴됨
    • 종료 시그널 감지 시 취소 함수 실행
ctx, cancel := context.WithCancel(context.Background()) defer cancel()

신호 감지

  • ctrl + c -> 운영체제에서 취소 신호(interrupt 신호)를 프로세스로 넘겨줌
  • signal.Notify는 신호를 감지하면 받아서 sigChan 이라는 채널로 넘겨줌
sigChan := make(chan os.Signal, 1) 
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

신호 감지 고루틴

  • 신호가 들어올때까지 sig := <- sigChan은 기다림
    • Buffered Channel 로 생각하면 될듯
  • 취소 신호(interrupt 신호)를 감지하면 signal.Notify로 sigchan 채널에 취소 신호를 넘김
  • sigChan 채널의 값을 sig 변수에 채널을 담고, 해당 블록은 해제됨
  • 로그를 찍은 후 컨텍스트 취소 메서드를 실행
go func() { 
    sig := <-sigChan 
    log.Printf("Received signal %s, shutting down...", sig) 
    cancel() 
}()

컨텍스트 완료 대기

  • 컨텍스트 취소 함수 (cancel())가 실행되기 전까지 대기 (블록)
  • 신호 감지 고루틴을 통해 취소 함수가 실행되면 대기중인 context의 채널이 닫힘
    • 이렇게 되면 해당 context를 사용중인 모든 함수들에 취소 신호가 전파됨
  • 컨텍스트가 취소되면 ctx.Done() 이 실행됨
<-ctx.Done()

완성본

  • 전체 코드와 흐름은 아래와 같다.
  • 취소 신호를 받아 컨텍스가 취소되기 전까지 실행할 함수를 넣어주면 된다.
    • 실행할 함수에도 취소 가능한 context(ctx) 를 넘겨줘서, Graceful 하게 고루틴을 종료시킬 수 있음

func main() {
    // 취소 가능한 context 생성
    ctx, cancel := context.WithCancel(context.Background())

    defer cancel() // 종료 취소 보장

    // 신호 채널 생성
    sigChan := make(chan os.Signal, 1)

    // 취소 신호 감지 시 채널에 넘겨주는 용도
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigChan // 신호가 들어올 때 까지 대기. 신호가 들어오면 해제됨
        log.Printf("Received signal %s, shutting down...", sig)
        cancel() // context 취소 함수 실행
    }()

    // 실행할 함수
    lib.Download(ctx, url, fileName)

    <-ctx.Done()

    // 종료 로그 등, 종료
}
728x90
반응형
728x90
반응형

Golang은 뛰어난 동시성 지원이 장점이라고들 한다.

일반적으로 쓰레드를 활용하여 동시성 프로그래밍을 하지만, Golang은 고루틴 goroutine으로 가능하다.

 

고루틴 Goroutine

공시적인 설명으로는 경량 쓰레드이다. 실제 OS의 쓰레드를 사용하는 것이 아닌, golang의 런타임에서 관리되는 논리적 / 가상적 쓰레드이다.

 

사용법

go func() {

    // 대충 코드

  }()

이게 끝이다.

 

thread VS goroutine

1. 메모리 사용량

  • goroutine: 생성시 약 2KB의 스택 메모리 혹은 힙 메모리 공간만을 사용한다.
  • thread: 약 1MB 의 메모리 공간을 사용한다.
  • P.S. Guard Page: 쓰레드가 사용할 메모리 공간과 각 메모리 간의 경계 역할
  • 따라서 스레드 기반의 동시성 처리는 결국 메모리 부족 현상(OutOfMemory) 문제 발생할 수 있으므로, 스레드를 미리 생성해 재활용하는 형태가 될 수 있음.

 

2. 비용

  • goroutine: 런타임에서 논리적으로(하드웨어와 상관 없이) 생성되고 소거되기 때문에 가볍다.
  • thread: OS 에 스레드 생성을 요청해 사용하고 작업 완료 시 다시 OS에 반환해야 한다.
  • 따라서 쓰레드 생성시마다 OS에 요청해 생성하고 다시 반환하는 방식이 더 리소스 사용량이 많다.

 

3. Context Switching 비용

  • 하나의 스레드가 특정 작업 처리를 위해 사용(Blocking)된다면, 다른 쓰레드가 대신하여 처리하도록 스케쥴링 됨
  • goroutine: 3개의 레지스터(PC(Program Counter)), SP(Stack Pointer), DX)만 save/restore 작업을 함
  • thread: 스레드가 스케쥴링고 교체되는 동안 스케쥴러에서는 모든 레지스터들을 save/restore 해야함
  • 일반적으로 16개의 범용 레지스터, PC, SP, Segment 레지스터, 16개의 XMM레지스터, FP coprocessor state, 16개의 AVX 레지스터, 모든 MSR들 등 save/restore 작업을 진해해야함
  • 따라서 Context Switching 할 때 save/restore 해야하는 레지스터의 개수부터가 큰 차이가 남

 

결론

golang이 메모리 사용량과 쓰레드 생성과 작업처리에 있어서의 비용 측면에서 효율적이다.

 

마지막

goroutine의 동작 방식과 쓰레드 종류에 대해선 다음에 서술하고자 한다.

 

REFERENCE

- https://velog.io/@khsb2012/go-goroutine

 

 

728x90
반응형

+ Recent posts