- Go 런타임이 관리하는 논리적 단위의 가상 쓰레드
- OS 쓰레드와는 조금 다른데, OS 쓰레드보다 가볍고 빠른 비동기 처리를 위해 Goroutine을 개발했다고 한다
- OS 쓰레드는 보통 1MB 메모리를 차지하는데, Goroutine은 1KB 정도만 사용한다고 한다
- OS 쓰레드는 생성하고 반납을 다시 OS에 반납해야 하는 과정이 있는데, Goroutine은 Go 런타임에서 관리하기에 저렴하다고 한다
- Goroutine이 가볍긴하지만 너무 남발하면 시스템이 느려질 수 있고, 특정 Goroutine이 문제가 생기면 Panic이 발생하여 시스템이 뻗을 위험도 있다
func say(s string) {
fmt.Println(s)
}
func main() {
go say("111")
go say("222")
go say("333")
time.Sleep(time.Second * 3)
// 이 구문이 없으면 go 실행을 확인할 수 없음과 동시에 종료
}
구조
- Go는 프로그램 시작부터 끝까지 런타임 내 Goroutine들을 관리하는데, 기존 쓰레드, 쓰레드 풀 방식이 아닌 M:N 방식이라 한다
- 원래는 1개의 OS 쓰레드 위에 N개 유저레벨 쓰레드를 실행시킨다는데, M:N은 혼합형태로 Goroutine을 스케줄링한다고 한다
GMP 모델
- G : Goroutine, 기능이 실행되는 쓰레드
- M : Machine, OS 쓰레드, 실제 OS 쓰레드는 아닌 논리적 단위 표현
- P : Processor, Machine 위 Goroutine이 실행할 수 있게 해주는 리소스, 실제 Processor가 아닌 논리적 단위 표현
- LRQ : Local Run Queue, Processor에 종속된 Goroutine 관리 큐
- GRQ : Global Run Queue, LRQ에 할당되지 못한 Goroutine들을 관리한다
스케줄러
- 위 GMP 구조로 Goroutine의 스케줄러가 실행된다
- 하나의 Machine이 Processor를 통해 하나의 Goroutine을 실행한다
- 특정 Goroutine이 완료되었으면, LRQ에서 다른 Goroutine을 실행, 이 과정을 반복한다
- LRQ 내 Goroutine이 없다면, 다른 LRQ에 Goroutine을 가져가 실행, 또 없다면 GRQ에 있는 Goroutine을 실행한다, 이를 Work Stealing, 가로채기라 한다
- Processor는 GO 환경변수인 GOMAXPROCS 값을 통해 조정할 수 있다
- 실행 중 syscall이 발생(I/O 작업류)하면 blocking이 발생할 수 있는데, Go에선 이를 피하기 위해 다른 쓰레드, 머신으로 넘겨 정상 처리할 수 있도록 한다
- syscall이 끝난 Goroutine은 잠시 넘겨주었던 Processor로 넘어가거나 아예 GRQ에 적재되기도 한다