공부하고 기록하는, 경제학과 출신 개발자의 노트

학습일지/Language

GopherCon 2023 - the Secret life of Goroutine

inspirit941 2024. 11. 30. 18:33
반응형

https://youtu.be/MYtUOOizITs?si=bUId7ieorNpUOvJD

 

Jesús Espino: mattermost (slack의 오픈소스 버전) 소속 Software Engineer

 


goroutine을 설명하려면, go의 scheduler 이해가 필요하다.

go scheduler를 구성하는 컴포넌트부터 간단히 소개하자면

스크린샷 2024-11-29 오후 1 37 41

 

Processor (alias P): Representation of Virtual CPU

  • goMaxProcs: number of Ps that scheduler have.
  • Status 정보 가지고 있음 - Idle, Running, Syscall, gcStop
  • Associated the Current 'M'
  • 개별 P는 본인이 실행할 GoRoutine정보를 관리할 Queue가 있음.
  • List of Free GoRoutines
  • other metadatas..

스크린샷 2024-11-29 오후 1 47 04

 

Machine (alias M): OS의 Thread. 코드를 실행하는 CPU 구역.

  • M에서 실행중인 Current Goroutine정보 포함.
  • M에 연결되어 있는 Processor P 정보
    • 연결된 P 정보가 null인 경우도 있다.
  • other metadatas..

스크린샷 2024-11-29 오후 1 49 54

 

Scheduler (alias Sched)

  • Idle Ms / Idle Ps 정보 가지고 있음.
  • List of Global runnable goroutines
    • 아직 아무 process에도 할당되지 않은 goroutines들
  • List of Global Free goroutines

스크린샷 2024-11-29 오후 1 54 30

 

Goroutine (alias G)

  • Stack (2048 bytes = 2KB chunk of memory)
  • program counter (thread의 program counter와 비슷한 역할)
  • status: 현재 goroutine의 상태 (Running, Waiting, Runnable...)
  • Current M: goroutine이 실행되는 machine정보
  • Wait Reason: waiting상태일 때 reason정보가 기록되는 곳
  • other metadatas...

스크린샷 2024-11-29 오후 1 59 02

 

구성요소들을 가지고 Scheduler의 모습을 설명하자면 위와 같다.

  • Scheduler는 List of Free goroutines / runnable goroutines 정보와 '어떤 goroutine이 어느 P, M에서 실행되는지를 알고 있다.
  • Global Runtime 변수로 List of Ms / Ps / Gs 가지고 있음.

The Birth of a goroutine

스크린샷 2024-11-29 오후 2 11 10

 

goroutine 생성 방법은 크게 두 가지.

  • create it from scratch.
  • reuse an old goroutine thia is no longer working.

goroutine 실행이 끝나면 Status는 Dead가 된다. Free goroutines는 전부 Dead 상태임.

  • free goroutine이 있다면, dead상태인 걸 reuse
  • free goroutine이 없다면 새 goroutine 생성 -> kill -> dead상태인 것 사용함.

스크린샷 2024-11-29 오후 2 15 01

 

새로운 goroutine 실행이 필요하다?

  • pick one of the free goroutine in Free List
  • raise up from the dead
  • put into the queue of runnable goroutines of Processor.
  • call the scheduler -> scheduler Execute that goroutine.

스크린샷 2024-11-29 오후 2 27 51스크린샷 2024-11-29 오후 2 28 22

 

만약 해당 processor에 free goroutine이 없다면?

  • global goroutine queue에서 free인 것 가져온다. (다른 processor에 할당되지 않은 상태인 것)
  • raise up from the dead -> add to queue

스크린샷 2024-11-29 오후 2 31 10스크린샷 2024-11-29 오후 2 31 19

 

global goroutine list에서도 free인 게 없다면?

  • 내부에서 신규 생성 -> kill -> raise up dead & add queue

The life of a goroutine

스크린샷 2024-11-29 오후 2 32 23스크린샷 2024-11-29 오후 2 33 02

 

Runnable to Running: scheduler가 신규 goroutine을 활성화할 때.

  • scheduler가 실행가능한 goroutine을 찾는다.
    • local process 내부에서 찾기
    • global runnable queue에서 찾기
    • 여기에도 없으면 net poll로 간다.
      • net poll: system that allows go to do IO work, in a efficient way. IO작업 끝나면 get goroutine runnable again.
  • goroutine을 찾아서 machine (OS Thread)에 할당하면 Running으로 변경
  • 이후 코드 실행.

스크린샷 2024-11-29 오후 2 33 12

 

Runnable to Waiting: Go Concurrency의 핵심.

  • 예컨대 channel is not buffered / have to wait for something...
    • goroutine 스스로 자신의 state를 wait로 변경, wait reason에 이유 작성
    • detach from Machine
    • run the scheduler (to schedule a New goroutine)

스크린샷 2024-11-29 오후 2 33 20스크린샷 2024-11-29 오후 3 43 57

 

go 소스코드에서 확인할 수 있는 Wait Reason의 전체 목록. 요약하면 아래와 같다.

  • GC
  • Mutex
  • Semaphore
  • Channel
  • Sleep
  • IO

스크린샷 2024-11-29 오후 3 44 40

 

Running to Syscall / to runnable

  • syscall: OS에 명령어 실행 요청하는 것.
    • 요청할 경우 detach from Processor.
  • 실행요청 시 state를 syscall로 변경.
  • syscall 요청이 끝나면 runnable로 변경 + queue에 전달.

스크린샷 2024-11-29 오후 3 51 20

 

Running to CopyStack / back

  • 실행과정에서 stack크기를 키워야 할 경우 state가 변경됨.
  • inMemory stack 크기를 2배 증설 -> 할당된 데이터들 copy
  • 복사 완료되면 다시 running으로 변경

스크린샷 2024-11-29 오후 3 53 09

 

Waiting to Runnable

  • 다른 goroutine이 goready를 호출할 때.
    • 예컨대 channel에서 메시지 받으려고 대기중인 goroutine이 있고 다른 goroutine이 channel에 메시지를 보낸다면, 메시지 보낸 뒤 wake up하는 식
  • added to queue in processor
  • try to get processor to execute

스크린샷 2024-11-29 오후 4 02 14

 

GC 끝나서 다시 goroutine 실행할 경우

  • Reactivate a list of goroutines
  • runnable로 상태 변경
  • queue에 추가 + 필요한 processor에 할당

스크린샷 2024-11-29 오후 4 04 18

 

wait가 필요한 로직이라서 waiting으로 변경했는데, 확인해보니 그럴 필요가 없는 경우

  • i'm going to wait for X, but X is already fulfilled.

스크린샷 2024-11-29 오후 4 05 15

 

goroutine 찾으려고 netpoll까지 가는 경우

 

스크린샷 2024-11-29 오후 4 09 07

 

Running to preemmpt, waiting and runnable

  • go는 preemptive GC / preemptive runtime이 있다.
    • 예컨대 특정 goroutine이 너무 오랜 시간 실행되고 있으면, scheduler가 goroutine 실행되는 OS thread에 signal 보낸댜.
    • 그러면 goroutine은 preempt로 변경됨.
  • preempt 이후 waiting으로 바뀌며, GC 등 필요한 프로세스가 끝난 뒤 다시 runnable로 변경됨.

Example

스크린샷 2024-11-29 오후 4 13 49

 

예컨대 channel이 하나 있고, goroutine에서 데이터를 넘겨주면 다른 goroutine에서 데이터를 받아 실행하는 구조를 생각해보자. channel은 buffer가 없는 상태.

  • channel struct에 정의된 list of goroutine에 자기 자신을 밀어넣고 waiting 상태로 진입.
  • channel에서 데이터를 읽으려는 다른 goroutine이 진입
    • read the data directly from the memory of the other goroutine.
    • 필요한 작업이 끝나면, waiting 상태에 있는 goroutine에 goready 호출.
  • data를 전달해준 goroutine은 goready 받았으므로 runnable로 변경, data를 받은 쪽은 프로세스 마저 실행.

스크린샷 2024-11-29 오후 4 24 52

 

WaitGroup으로 3을 정의할 경우

  • waitgroup 내에서 goroutine이 실행되는 동안, 메인 프로세스는 waiting
  • waitgroup 개수만큼 있던 goroutine이 전부 done이 되면, waiting중인 프로세스에 getReady 명령어를 보낸다.
  • getReady 받은 메인 프로세스는 waiting -> runnable로 변경됨.

the Death of a goroutine

스크린샷 2024-11-30 오후 6 04 53

 

  • 기본적으로는 '작업이 끝나면' 죽는다.
  • state를 dead로 바꾸고
  • goroutine을 Machine에서 할당 해제하고
  • free list of P에 goroutine 정보 추가
  • call scheduler

Summary

스크린샷 2024-11-30 오후 6 16 16스크린샷 2024-11-30 오후 6 16 21스크린샷 2024-11-30 오후 6 16 29

 

  • scheduler의 동작과정에서 뻬놓을 수 없는 게 go의 GC인데, 발표가 너무 길어져서 뺐다.
  • netpoll도 언급만 하고 자세한 내용은 소개하지 않았음.
  • cgo도 goroutine 활용해서 구현됐는데 소개하지 않음
  • mark assist: gc중에 수행되는 보조 역할 / goroutine이 하는 일 중 하나.
  • sysmon (system monitor)...

스크린샷 2024-11-30 오후 6 21 48

 

https://youtu.be/KxOwt6z0FvY?si=V5LuJCPp5y_Qfxa2

반응형

'학습일지 > Language' 카테고리의 다른 글

EuroPython 2022 - From Pip to Poetry: Python ways of packaging and publishing  (0) 2023.06.11
Writing Beautiful Package in Go  (0) 2022.05.29
Go - Context 정리  (0) 2022.05.26
JIT Compiler  (0) 2021.03.11
[Design Pattern] Facade  (0) 2020.12.28