웹 어셈블리?
웹 어셈블리(Web Assembly, 이하 WASM)란, 웹에서 높은 성능의 대화형 애플리케이션을 만들기 위한 참신한 기술이다. WASM은 웹 브라우저에서 실행되는 바이너리 코드 형식으로, JavaScript보다 속도가 빨라 빠른 계산을 요구하는 작업에서 적용되고 있는 기술이다.
Go 언어에서도 이러한 움직임에 맞춰 1.11 버전부터 WASM 컴파일을 지원하기 시작했고 1.21 버전까지도 지속적으로 발전해오고 있는 중요한 트렌드 중 하나이다.
이러한 웹 어셈블리에는 중요한 내용이 있는데 바로 이것…
웹 어셈블리는 멀티 스레드를 지원하지 않습니다.
그래서 이 내용을 보고 Go 언어에서 고루틴이 어떻게 적용되는지 조사한 내용을 블로그에 작성해본다.
Go Runtime
웹 어셈블리를 target 으로 컴파일한 프로그램에서 고루틴이 어떻게 동작할지는 runtime 패키지의 debug.go 의 소스코드에서 힌트를 얻을 수 있다.
debug.go 12~19 라인 내용 발췌
// GOMAXPROCS sets the maximum number of CPUs that can be executing
// simultaneously and returns the previous setting. It defaults to
// the value of [runtime.NumCPU]. If n < 1, it does not change the current setting.
// This call will go away when the scheduler improves.
func GOMAXPROCS(n int) int {
if GOARCH == "wasm" && n > 1 {
n = 1 // WebAssembly has no threads yet, so only one CPU is possible.
}
// ... 생략
Go
복사
GOMAXPROCS 함수에 적힌것처럼 아키텍처가 웹 어셈블리로 확인된다면 함수에 어떤 값을 전달하던 간에 강제로 procs 개수를 1로 조정한다.
다음의 코드는 예시인데, GOMAXPROCS 함수를 이용하여 최대 프로세서(procs) 개수를 1개로 설정한 후, 2개의 고루틴을 생성하여 채널로 송수신하는 코드이다.
얼핏 보면 이 코드는 Deadlock 상태에 빠질 것 처럼 보인다. 동작할 CPU 개수를 1개로 고정시켰으니, 병렬로 처리가 안될거같고, 고루틴은 송신할때 수신측이 없거나, 수신할때 송신측이 없다면 Block 상태를 유지하기 때문이다.
하지만 이 코드는 실행해보면 정상적으로 모든 코드를 수행하고 종료되는 걸 확인해볼 수 있다.
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
runtime.GOMAXPROCS(1)
wg := sync.WaitGroup{}
c := make(chan bool, 0)
wg.Add(1)
go func() {
defer wg.Done()
c <- true
fmt.Println("goroutine 1 done.")
}()
wg.Add(1)
go func() {
defer wg.Done()
<-c
fmt.Println("goroutine 2 done.")
}()
wg.Wait()
fmt.Println("All goroutine done.")
}
Go
복사
이 이유에 대해서 설명하자면 다음과 같다.
Go 언어 심화 강의에 나중에 설명할 내용인데 Go 언어는 GMP 모델이라고 하여 프로세서의 개수만큼의 Native Thread를 매핑해놓고 런타임이 스케줄링해주는 방식으로 운영된다. 이 프로세서의 개수가 바로 procs 이다.
Go 런타임의 스케줄러는 운영체제의 기본 스레드(Native Thread)와 고루틴(Goroutine) 간의 관계를 M:N으로 매핑하도록 설계되어 있다. 적은 수의 스레드(Native Thread)에서도 다수의 고루틴이 동작할 수 있게 하며, 이는 스케줄러가 지속적으로 고루틴을 스케줄링하며, 필요에 따라 스레드 간에 작업을 전환하는 방식으로 이루어진다.
덕분에 실제로는 구동되는 스레드가 1개이지만 우리가 여러개의 고루틴을 사용할 수 있는 것이다.
결론
따라서 우리가 Go 언어를 WASM 타겟으로 컴파일하여 사용하려고 할 때, 이미 사용하고 있는 고루틴들을 무리하게 최적화하여 단일 스레딩 모델화할 필요는 없다.