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
반응형

Goroutine

  • Go 런타임이 관리하는 가상(논리) 쓰레드
    • go 키워드로 고루틴 실행 가능
  • 비동기적으로 함수 루틴을 수행하며, 동시 동작 수행에 사용됨
  • OS 스레드보다 훨씬 가볍고, 생성할 때 비용이 적음
    • OS 스레드는 1mb의 메모리 스택, goroutine은 kb 단위 (동적 증가 가능)
  • 기본적으로 CPU 1개를 시분할하여 사용

다중 처리

  • Go는 기본적으로 CPU 개를 사용한다
  • 다중 병렬 처리를 위해 CPU를 여러개 사용하기 위해선 GOMAXPROCS로 증가시킬 수 있다.
runtime.GOMAXPROCS(4) // CPU 4개 사용

익명함수 (go func)

  • 익명 함수 go func() {}() 으로 비동기 실행 가능
func main() {  
  var wait sync.WaitGroup // Go 루틴 대기열 두개 생성  
  wait.Add(2)


  go func (parameter string) {
      defer wait.Done()
      //
  } ("parameterValue")

  wait.Wait() // Go 루틴 끝날 때 까지 대기
 }

Graceful Shutdown

  • Go 언어에서 여러 고루틴을 운영할 때 Graceful Shutdown 로직이 필요한 이유는, Go 애플리케이션이 종료 신호를 받았을 때 하위 고루틴들이 메인 고루틴보다 먼저 종료될 가능성이 있기 때문
  • 쉽게 말하면, 작업 지시서이다

Context

  • 동시성을 관리
  • 여러 goroutine에 값을 전달
  • 취소 신호 전파
  • 분산 시스템 / 서비스 간의 상호 작용에서 아용

context.Background

  • 모든 context 의 기본이 되는 빈 context 를 반환
  • 대부분의 context 는 이를 기반으로 생성

context.TODO()

  • 아직 구현되지 않은 부분을 나타내는 context 를 반환
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