Goroutine, GMP Model

· 3분 읽기

Goroutine과 GMP 모델: Go 런타임의 스케줄링 아키텍처

Go 언어의 가장 강력한 특징 중 하나는 경량 동시성 스레드로 불리는 Goroutine 이다.

수십만 개의 Goroutine 을 동시에 실행할 수 있는 이유는 Go 런타임의 정교한 스케줄러 덕분이다.

물론 스케줄러의 탁월함에 대해서는 이견의 여지가 많지만 여기서는 다루지 않는다

Go 에서는 M:N 스케줄링 모델과 GMP 아키텍쳐를 차용했다.

M:N 스레딩 모델 비교

프로그래밍 언어의 동시성 모델은 user-level 스레드와 kernel-level thread 를 어떻게 매핑하느냐에 따라 크게 세가지로 분류된다.

1:1 모델

각 유저 스레드가 커널 스레드에 1대 1로 매핑된다.

Pros

  • True parallelism

  • 구현이 단순하다

Cons

  • 컨텍스트 스위칭 비용이 비싸다. 1대1 이니까 커널 수준에서 발생한다.

  • 스레드 생성/소멸 비용이 커진다.

N:1 모델 (Green Threads)

여러 사용자 스레드가 하나의 커널 스레드에서 실행된다

Pros

  • 빠른 컨텍스트 스위칭 (사용자 공간에서 처리된다)

  • 낮은 메모리 사용량

Cons

  • 하나의 스레드가 블로킹되면 전체가 블로킹된다

  • True parallelism 은 아님

M:N 모델

M 개의 Goroutine 을 N 개의 OS 스레드에 매핑하는 하이브리드 방식이다.

Goroutine 은 OS 스레드가 아니다. OS 스레드 위에서 실행된다.

Pros

  • 대규모 동시성이 가능하다

  • 낮은 메모리 오버헤드

  • 빠른 컨텍스트 스위칭

Cons

  • 복잡한 스케줄러가 필요하다

  • 블로킹 시스템 콜 처리 비용

GMP 아키텍쳐

Go 런타임 스케줄러는 세 가지 핵심 구성 요소로 이루어져 있다.

G(Goroutine) + M(Machine/OS Thread) + P(Processor)

Goroutine 은 사용자 공간에서 실행되는 경량 태스크이다.

Machine(OS Thread)은 실제 Goroutine 을 실행하는 OS 스레드를 말한다.

머신이 고루틴을 실행하려면 바느시 P에 바인딩 되어야한다.

최대 개수는 기본 10,000개인데, debug.SetMaxThreads 로 설정도 가능하다.

P는 논리 프로세서로, G와 M을 연결하는 다리 역할이다. 그 유명한 GOMAXPROCS 환경 변수가 이것의 개수를 말한다.

스케줄링 동작 원리

Go 스케줄러는 협력형과 선점형의 하이브리드 방식으로 동작한다.

협력형 스케줄링 (Pre-Go 1.14)

Goroutine 이 자발적으로 실행권을 양보하는 시점:

  • 함수 호출시

  • 채널 작업

  • go 키워드 실행

  • 블로킹 시스템 콜

  • GC Safe point

문제점: 함수 호출 없이 오래 실행되는 CPU 집약적 코드는 P를 독점하는 경우가 생겼다.

// Pre-Go 1.14: 이 코드는 P를 독점
for i := 0; i < 1000000000; i++ {
    // 함수 호출 없음
}

선점형 스케줄링 (Go 1.14+)

비동기 선점을 도입하여 위 문제를 해결했다. 동작방식은:

  1. sysmon 이라는 고루틴이 백그라운드에서 모니터링

  2. 고루틴이 10ms 이상 실행되면 선점 대상으로 표시

  3. M의 OS 스레드에 SIGURG 시그널 전송

  4. 시그널 핸들러가 레지스터 저장 후 스케줄러로 제어 이동

SIGURG: 소켓에 긴급한 데이터가 도착했을 때 해당 소켓의 소유자 프로세스에 전송되는 비동기 신호

Work Stealing 알고리즘

말 그대로 훔쳐오는 알고리즘이다. P가 실행할 고루틴이 없을 때 다른 P의 작업을 훔친다.

  1. runnext 확인

  2. (없으면) Local Run Queue (LRQ) 에서 pop

  3. (없으면) Global Run Queue (GRQ) 확인

  4. (없으면) 다른 P의 LRQ 에서 절반을 훔쳐옴 (Randomly)

  5. (없으면) Network poller 확인 (I/O 완료된 Goroutine)

  6. (없으면) M을 Park (유휴 상태로 전환)

Go's work-stealing scheduler · rakyll.org

References