[Go] Q: 고루틴은 어떻게 동작하나요? (번역)

https://stackoverflow.com/questions/24599645/how-do-goroutines-work-or-goroutines-and-os-threads-relation


질문
고루틴은 syscall을 호출하는 동안 실행을 어떻게 유지하나요? GOMAXPROCS=1일때 말이에요.
제가 깨달은 것에 의하면, 스레드가 syscall을 호출하면 그 스레드는 syscall이 반환될 때까지 제어권을 포기할텐데요.
Go는 어떻게 syscall로 블럭되는(blocking-on-syscall) 고루틴마다 시스템 스레드를 생성하지 않고 병행성을 달성하나요?

문서를 보면 이렇다네요.
고루틴
고루틴은 왜 고루틴이라 부르는가? 기존의 명칭인 스레드나 코루틴, 프로세스 등과 완전히 부합하지 않기 때문이다.
고루틴은 아주 간단한 모델을 가진다. 그냥 같은 주소공간에서 다른 고루틴들과 함께 병행적으로 실행되는 함수인 것이다. 게다가 고루틴은 함수를 따로 스택 공간에 할당하는 것보다 더 가볍고, 비용도 적다.
고루틴의 스택은 아주 작게 시작해서, 저렴한 비용을 가진다. 필요하다면 힙을 할당해서 확장하기도 한다.
고루틴은 여러개의 OS 스레드로 다중화된다. 그래서 만약 고루틴 하나가 I/O를 기다리는 등의 행동으로 블럭되더라도, 다른 고루틴들은 그래도 실행을 지속한다.
그래서 사실 고루틴의 디자인 뒤편에는 스레드의 생성이나 관리에 대한 이런저런 난장판이 펼쳐져있다.



답1
고루틴이 블럭된다면, Go의 런타임은 다른 고루틴을 핸들링하기 위해 새로운 OS 스레드를 생성합니다. 그 블럭된 고루틴이 살아날 때까지만요.

이걸 참조하세요: https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ



답2
오! 이건 제가 배웠던 거네요.
생(raw) syscall을 호출하면 Go는 블럭된 고루틴마다 스레드를 생성합니다.

예제를 한번 봐보세요.

package main
import ( "fmt" "syscall" )

func block(c chan bool) {
    fmt.Println("block() enter")
    buf := make([]byte, 1024)
    _, _ = syscall.Read(0, buf)
ㅤ// STDIN에서 읽어올때까지 블럭
    fmt.Println("block() exit")
    c <- true // main()은 이미 끝남
}

func main() {
    c := make(chan bool)
    for i := 0; i < 1000; i++ {
        go block(c)
    }
    for i := 0; i < 1000; i++ {
        _ = <-c
    }
}

이걸 실행해보면, 우분투 12.04는 저 프로세스에 1004개의 스레드를 생성합니다.

반면에, Go의 Http 서버를 사용해서 1000개의 소켓을 열어봤는데요. 이건 4개의 os 스레드만을 생성했습니다.

package main
import ( "fmt" "net/http" )

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

이건 블럭되는 syscall 마다 IO 루프와 스레드가 섞입니다.