main_function

[Go/Golang] 2. 고루틴(goroutine) Deep Dive 본문

Programming

[Go/Golang] 2. 고루틴(goroutine) Deep Dive

MAIN 2021. 1. 13. 20:30

CSP (Communicating Sequential Processes)

Go언어, 고루틴을 다루면서 대표적으로 듣는 컨셉이 CSP 입니다. 저 자체가 논문의 제목이자 Go 언어가 동시성을 모델링한 방식입니다.

프로그래밍에서 두 가지 기본 요소인 입력과 출력이 간과하고 있고, 특히 동시에 실행되는 코드의 경우에 더욱 그렇다 라는 접근 방식이 CSP의 핵심이라고 합니다. Go 언어는 CSP의 핵심 원칙을 통합한 최초의 언어 중 하나라고 합니다.

I/O 제어와 프로세스 통신이 Go의 동시성 프로그래밍의 기반입니다. 공유 메모리 모델을 대규모 프로그램이나 복잡한 프로그램에서 올바르게 활용하기 어렵고, 제약사항도 많기 때문에 이러한 방식을 택했다고 알려져 있습니다.

 

아래의 내용이 Go 언어의 동시성 핵심 철학입니다.

메모리 공유를 통해 통신하지 말라! 대신 통신을 통해 메모리를 공유해라!

단순화를 목표로 하고, 가능하면 채널을 사용하고 고루틴을 무한정 쓸 수 있는 자원처럼 다루어라!

 

 

goroutine은 coroutine 중 하나이다.

고루틴은 OS Thread도 아니고 Green Thread도 아닙니다.

  • Green Thread
    주로 runtime library나 VM에서 사용하는 방식으로 대표적으로 JVM 내에서 생성하는 스레드(user-level thread)가 있습니다. JVM에 의해 컨트롤되며 1.3 버전 이전까지는 1:N 맵핑의 완전 user-level thread였지만 현재는 Kernel Thread와 1:1 맵핑되는 방식입니다.
    그린 스레드는 원래 자바 스레드 관련 라이브러리 이름이었고 1.1 ~ 1.3버전까지 쓰다가 사라지고 그린 스레드라는 이름만 통용되고 있는 상태입니다.

goroutine은 coroutine이라고 불리는 더 높은 수준의 추상화입니다. 코루틴은 동시에 실행되는 서브루틴(함수, 클로저 등)으로서, 비선점적(Non-preemptive)이라서 중간에 끼어들 수 없습니다. 대신 코루틴은 중단(suspend)이나 재진입(reentry)할 수 있는 여러 지점을 가지고 있습니다. 단, goroutine은 block 상태일 때만 중단이 가능하다는 점이 차이점입니다.

 

goroutine의 장점

고루틴은 OS 스레드보다 가벼운 경량 스레드입니다. 자바의 스레드의 경우 약 1MB 메모리가 필요하고 거기에 스레드끼리의 여유 공간인 guard page까지 필요한데 반해 고루틴은 2KB(v1.3기준)의 스택 공간만 차지합니다. 

또한 Context Switching 시 일반적인 스레드는 16개의 범용 레지스터, PC, SP, segment 레지스터, 16개의 XMM 레지스터 등 많은 것을 저장하고 복구해야 하지만 고루틴은 3개의 레지스터(PC, SP, DX)만 저장 및 복구하면 되기 때문에 컨텍스트 스위칭 비용이 매우 적습니다. 이게 만얀 무거웠다면 N개의 OS 스레드에 M개의 고루틴을 계속 스케줄링하면서 실행하는 데에 큰 부하가 있었을 것입니다.

위와 같은 이유들 때문에 고루틴을 경량 스레드라고 불리고 있으며 초기 스타팅 타임도 꽤 빠르다고 알려져있습니다.

 

고루틴은 OS thread와 M:N 맵핑

Go의 경우 자바의 concurrency model과 마찬가지로 fork-join 모델을 입니다. fork로 분기하여 main 고루틴과 sayHello 고루틴이 동시에 실행되는 방식이죠.

func main() {
    var wg sync.WaitGroup
    sayHello := func() {
        defer wg.Done()
        fmt.Println("Hello")
    }
    
    wg.Add(1)
    go sayHello()
    
    wg.Wait()
}

 

위 코드를 그림으로 나타낸 모습입니다. sayHello라는 함수를 고루틴으로 만들어줍니다. sync 패키지의 WaitGroup을 사용하지 않으면 높은 확률로 sayHello가 실행도 안된 상태로 프로그램이 종료될 것입니다. (물론 이것도 경우에 따라 달라지겠죠) 이를 방지하기 위해 명시적으로 해당 고루틴이 끝날 때까지 기다림으로써 적절한 합류 지점을 설정합니다.

 

Go scheduling

Go는 내부적으로 OS thread를 지칭하는 m이라는 구조체와 processor 컨텍스트를 일컫는 p구조체, 그리고 마지막으로 고루틴을 지칭하는 g구조체로 구성되어 있습니다. 실제 내부 코드는 공식 레포 링크를 달아뒀으니 참고해주시기 바랍니다.

고루틴은 OS 스레드와 M:N 맵핑이기 때문에 스케줄링을 위해서는 이를 담고 있을 큐가 필요하다. 그래서 각 프로세서(P)마다 Local Run Queue가 있고 여기 고루틴이 담겨있다. 코드 상으로 봤을 때 runq 라는 슬라이스가 256인 것으로 보아 로컬 런큐의 사이즈는 256인 듯합니다.

이 프로세서(P)가 컨텍스트이기 때문에 free한 스레드(M)이 생기거나 시스템 콜 등으로 인한 블록킹이 발생했을 때 P가 스케줄링되어 다른 M으로 할당됩니다. 즉, 실행 중인 스레드가 blocking 상태가 되면 다른 스레드로 현재 컨텍스트를 그대로 넘겨서 처리할 수 있도록 하기 위해 P가 구현되어 있습니다. 이는 Go의 GOMAXPROCS 라는 값으로 조정이 가능합니다.

Global Run Queue는 스케줄링이 되지 않은 고루틴이나 블록킹 등의 이유로 실행이 멈췄다가 재개되는 고루틴들이 쌓이는 공간이고 이는 Go의 스케줄러에 의해 적당한 프로세스(P)의 Local Run Queue로 스케줄링됩니다.

Local Run Queue 간의 불균형이 발생할 경우 GOMAXPROCS 값을 고려하여 리밸런싱 작업도 수행됩니다. 이를 goroutine steal 이라고 표현하기도 합니다. 

 

 

 

 

 

 

 


Reference

www.yes24.com/Product/Goods/74820845?OzSrank=1

 

Go 동시성 프로그래밍

고성능의 멀티 코어 CPU, 클라우드 기반의 비동기 서비스 등 최근 트렌드를 고려하면 프로그램을 작성할 때 동시성을 고려하는 것은 필수 과정이다. 이 책에서는 Go 언어의 동시성 모델과 이론적

www.yes24.com

www.timqi.com/2020/05/15/how-does-gmp-scheduler-work/

 

How does GMP scheduler work

As we know that There is a runtime when Golang running. The runtime perform the scheduled tasks(goroutine) in user space rather than kernel, so it’s more lightweight. It do a better tradeoff be

www.timqi.com

tech.ssut.me/goroutine-vs-threads/

 

Goroutines vs Threads

Google이 Go 언어를 만들어낸 이후 많은 시스템 관리용 유틸리티, 서버가 Go로 짜여지기 시작했고 매 업데이트마다 엄청난 성능 향상과 발전으로 이제 어디서든 Go 언어로 짜여진 프로그램을 쉽게

tech.ssut.me

 

'Programming' 카테고리의 다른 글

[Go/Golang] 채널(channel)  (0) 2021.01.20
[Go/Golang] 1. 동시성(Concurrency)  (0) 2021.01.13
Go 언어(golang)의 특징  (0) 2021.01.09
Comments