Skip to content

고루틴 스케줄링

  • Go 런타임은 프로그램이 실행되는 내내 고루틴을 관리한다.

  • Go 런타임은 모든 고루틴을 다중화된 스레드들에 할당하고 모니터링하며, 특정 고루틴이 블록되면 다른 고루틴이 실행될 수 있도록 교체하는 일을 반복한다.

  • 이 말은 고루틴이 블록 되더라도 다중화된 스레드는 블록 되지 않는다는 것을 의미한다.

    image
  • CPU Core가 존재하고 있으며 OS Scheduler에 의해서 다수의 Thread가 Scheduling되어 동작하고 있는 모습을 나타내는 그림이다.

  • Network Poller는 Network를 처리하는 별도의 독립된 Thread를 의미한다.

  • Run Queue에는 GRQ (Global Run Queue)와 LRQ (Local Run Queue)가 2가지가 존재한다.

    • GRQ는 의미 그대로 전역 Goroutine Queue 역할을,
    • LRQ는 의미 그대로 지역 Goroutine Queue 역할을 수행한다.

GMP 구조체

  • G (Goroutine) : 고루틴을 의미

    • 런타임이 고루틴을 관리하기 위해서 사용한다.
    • 컨텍스트 스위칭을 위해 스택 포인터, 고루틴의 상태 등을 가지고 있다.
    • G는 LRQ에서 대기하고 있다.
  • M (Machine) : OS 스레드를 의미

    • M은 P의 LRQ로부터 G를 할당받아 실행한다.
    • 고루틴과 OS 스레드를 연결하므로 스레드 핸들 정보, 실행중인 고루틴, P의 포인터를 가지고 있다.
  • P (Processor) : 논리 프로세서를 의미

    • P는 컨텍스트 정보를 담고 있으며, LRQ를 가지고 있어서 G를 M에 할당한다.
  • LRQ(Local run queue) : P마다 존재하는 Run Queue

    • P는 LRQ로 부터 고루틴을 하나씩 POP하여 실행한다.
    • P마다 하나의 LRQ가 존재하기 때문에 레이스 컨디션을 줄일 수 있다.
    • LRQ가 M에 존재하지 않는 이유는 M이 증가하면 LRQ의 수도 증가하여 오버헤드가 커지기 때문입니다.
  • GRQ(Global run queue) : LRQ에 할당되지 못한 고루틴을 관리하는 Run Queue

    • 실행 상태의 고루틴은 한번에 10ms까지 실행되는데, 10ms 동안 실행된 고루틴은 대기 상태가되어 GRQ로 이동됩니다.
    • 고루틴이 생성되는 시점에 모든 LRQ가 가득찬 경우 GRQ에 고루틴이 저장된다.
  • GMP 구조체는 runtime.runtime2.go에 구현되어 있다.

    image
  • Goroutine은 반드시 Processor(P)와 Thread(M)과 같이 존재할 경우에만 실행 상태가 된다. 따라서 동시에 최대로 구동시킬수 있는 Goroutine의 개수는 Processor(P)의 개수에 따라서 정해진다.

  • P는 LRQ에 존재하며, P의 컨텍스트 내에서 실행되도록 설정된 고루틴을 관리한다.

  • LRQ의 고루틴들이 P에 할당된 M에서 교대로 실행되도록 스케줄링 된다.

  • OS가 M에게 할당한 시간 동안 G 작업을 다 끝내면 M은 스피닝(Spinning, Busy waiting)하며 P의 LRQ의 맨앞에 있는 G를 가져온다.

고루틴 선택 (Poll order)

  • P가 선택할 수 있는 고루틴은 여러 가지가 있다.

    • Local Run Queue
    • Global Run Queue
    • Invoke Network poller
    • Work Stealing
  • 고루틴을 선택하는 다양한 방법이 존재하지만 크게는 아래와 같은 순서로 이루어진다.

    • P가 자신의 LRQ에서 고루틴을 POP
    • LRQ, GRQ가 비어 있으면 다른 P의 LRQ에서 고루틴을 POP (Work Stealing)
    • GRQ를 확인하여 고루틴을 POP

작업 훔치기 (Work Stealing)

  • M, P가 모든 일을 마치면 GRQ 또는 다른 P의 LRQ에서 일을 가져온다. 이를 통해서 병목 현상을 없애고 OS 자원을 더 효율적으로 사용한다.

  • 아래 예시를 살펴보자.

    image
  • P1, P2는 LRQ의 고루틴을 POP하여 작업을 수행한다.

    image
  • P1이 LRQ의 모든 작업을 완료한다. P2는 여전히 G1 작업을 처리 중이고, 이 시점에 작업 훔치기가 시도된다.

    image
  • P1은 P2의 LRQ를 확인하여 절반인 G3, G5를 가져와 작업을 계속 진행한다.

    image
  • 이번엔 P2가 작업을 완료했다. 그리고 작업 훔치기를 시도하기 위해서 P1의 LRQ를 확인하지만 실행 중인 고루틴을 제외하면 대기 중인 고루틴이 존재하지 않는다. 계속해서 P2는 GRQ를 확인하고 대기 중인 G9를 가져와서 작업을 진행한다.

    image
  • 이를 통해서 프로세서 간에 효과적으로 로드 밸런싱이 가능하며, M이 컨텍스트 스위칭 하는 것을 막을 수 있다.


참고