GoLang에서는 고루틴을 이용하여 뛰어난 성능의 어플리케이션을 구축할 수 있다. 고루틴의 개념, 동작 원리가 어떻게 되는지, 프로파일링 방법에 대해 알아보자.
우선 쓰레드라는 개념이 굉장히 헷갈리기 쉬워, 이에 대해 정리하려고 한다.
쓰레드는 여러가지를 지칭되어서 이를 정리해보았다.
쓰레드 종류
대부분 프로그래머가 제어할 수 있는 유저 레벨 쓰레드와, 이에 관계있는 커널 쓰레드를 집중해서 생각하면 좋다.
그렇다면, 쓰레드를 사용하는 이유는 무엇일까?
쓰레드 사용 이유
참고: 스레드 관리 - 개념, 구현(N:1, 1:1, N:M)
공식문서에서는 고루틴을 다음과 같이 소개한다.
A goroutine is a lightweight thread managed by the Go runtime.
공식문서에서는 고루틴을 다음과 같이 소개한다.
A goroutine is a lightweight thread managed by the Go runtime.
이와 비슷하게 Kotlin의 coroutine, Java의 Virtaul Thread도 경량화된 쓰레드(lightweight thread)라고 불린다. 경량화된 쓰레드는 무엇일까? 어떻게 경량화할 수 있었던걸까?
비교를 위해 Java의 Virtaul Thread에 관한 글을 참고하였다. (Java Thread API와 Virtual Thread가 대비하기 좋았다.)
요약해보자면, 일반적인 쓰레드인 Java Thread는 커널 쓰레드와 1:1 매핑을 만들었으나 경량화된 쓰레드인 Virtual Thread는 N:M 매핑을 만들어 사용했고 이를 통해 경량화가 되었다.
(Java Thread, 1:1 매핑된 모습)
(Java Thread, n:m 매핑된 모습)
1:1 매핑된 경우 발생하는 문제점은 다음과 같다.
1:1 매핑 구조의 문제점
경량화된 쓰레드에서는 n:m 관계로 자체적으로 쓰레드를 관리함으로써 위 문제를 해소했다.
고루틴을 관리하기 위해 스케줄러가 동작한다. 스케줄러는 아래와 같은 구조를 가지고 있다.
(스케줄러 구조)
자세한 설명은 아래 글들을 참고하자.
요약해보자면 아래와 같은 특징을 가지고 있다.
스케줄러의 특징
설명했듯이, goroutine은 Go runtime
에 의해 관리된다. 런타임은 자체적으로 goroutine 개수, 메모리 사용량 등의 정보를 유지한다.
이는 다양한 방법을 통해 확인할 수 있다.
runtime/debug
의 runtime.NumGoroutine()
Prometheus Metric
의 go_goroutines
net/http/pprof
이 중 프로파일링을 위한 세 번째 방법에 대해 설명하고자 한다.
package main
import (
"fmt"
"math/rand" "net/http" _ "net/http/pprof"
)
func main() {
go func() {
// 프로파일링 정보를 해당 포트로 내보낸다.
http.ListenAndServe("0.0.0.0:6060", nil)
}()
goroutineSize := 3
memoryUsageMB := 100
for i := 0; i < goroutineSize; i++ {
makeMemoryUp(memoryUsageMB, i)
}
select {}
}
func makeMemoryUp(memoryUsageMB int, i int) {
go func(id int, memoryUsageMB int) {
size := id + 1
memory := make([]byte, size*memoryUsageMB*1024*1024)
for j := range memory {
memory[j] = byte(rand.Intn(256))
}
fmt.Printf("Goroutine %d is using %d MB of memory\n", id, size)
select {}
}(i, memoryUsageMB)
}
위와 같은 간단한 코드가 있다. 3개의 고루틴에서 각각 100MB, 200MB, 300MB를 소모한다. 또한, 6060
포트가 열려있는데 net/http/pprof
패키지에서 해당 포트로 프로파일링 정보를 넘겨준다. (참고로 Google에서 사용하는 protocol buffer라는 형식으로 내보낸다.)
http://localhost:6060/debug/pprof/
을 방문하면, 다양한 프로파일 데이터들을 볼 수 있다.
또한 graphviz
라이브러리를 통해 프로파일 데이터들을 가시화할 수도 있다.
가시화한 결과는 아래와 같다.
(goroutine 숫자가 3개이다.)
(사용한 메모리 양이 의도한 대로 100MB, 200MB, 300MB이다.)
참고: NAVER D2
고루틴을 제대로 사용하기 위해 사전지식, 경량화, 동장 방식, 프로파일링에 대해 살펴보았다. 살펴보며 생각보다 깊은 내용들이 있었고, 메모리나 GC에 대해서도 같은 깊이의 많은 내용이 있다는 걸 알게되었다. 이들에 대해서도 다음에 살펴보아야겠다.