[concurrency] runtime.Gosched는 정확히 무엇을합니까?

에서 투어 이동의 웹 사이트의 이동 1.5의 출시 이전 버전 , 코드 조각 거기에 그 같은 모습.

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world")
    say("hello")
}

출력은 다음과 같습니다.

hello
world
hello
world
hello
world
hello
world
hello

나를 괴롭히는 runtime.Gosched()것은 제거 될 때 프로그램이 더 이상 “world”를 인쇄하지 않는다는 것입니다.

hello
hello
hello
hello
hello

왜 이렇게이다? 어떻게 runtime.Gosched()실행에 영향을?



답변

노트 :

Go 1.5부터 GOMAXPROCS는 하드웨어 코어 수로 설정됩니다 : golang.org/doc/go1.5#runtime , 1.5 이전의 원래 답변보다 낮습니다.


GOMAXPROCS 환경 변수를 지정하지 않고 Go 프로그램을 실행하면 Go 고 루틴이 단일 OS 스레드에서 실행되도록 예약됩니다. 그러나 프로그램을 멀티 스레드로 보이게하려면 (고 루틴의 용도가 그렇죠?) Go 스케줄러는 때때로 실행 컨텍스트를 전환해야하므로 각 고 루틴이 작업을 수행 할 수 있습니다.

앞서 말했듯이 GOMAXPROCS 변수가 지정되지 않은 경우 Go 런타임은 하나의 스레드 만 사용할 수 있으므로 goroutine이 계산이나 심지어 IO (일반 C 함수에 매핑되는 IO)와 같은 일반적인 작업을 수행하는 동안 실행 컨텍스트를 전환 할 수 없습니다. ). 컨텍스트는 Go 동시성 프리미티브가 사용되는 경우에만 전환 할 수 있습니다. 예를 들어 여러 채널을 켜거나 스케줄러에게 컨텍스트를 전환하도록 명시 적으로 지시 할 때 (이것이 귀하의 경우입니다)runtime.Gosched 목적입니다.)

즉, 한 고 루틴의 실행 컨텍스트가 Gosched호출에 도달 하면 스케줄러는 실행을 다른 고 루틴으로 전환하도록 지시합니다. 귀하의 경우에는 두 개의 고 루틴이 있습니다. 메인 (프로그램의 ‘메인’스레드를 나타냅니다)과 추가로 go say. Gosched호출 을 제거 하면 실행 컨텍스트가 첫 번째 고 루틴에서 두 번째 고 루틴으로 전송되지 않으므로 ‘월드’가 없습니다. 가있을 때 Gosched스케줄러는 각 루프 반복의 실행을 첫 번째 고 루틴에서 두 번째 고 루틴으로 또는 그 반대로 전송하므로 ‘hello’와 ‘world’가 인터리브됩니다.

참고로,이를 ‘협동 멀티 태스킹’이라고합니다. 고 루틴은 다른 고 루틴에 대한 제어권을 명시 적으로 양보해야합니다. 대부분의 최신 OS에서 사용되는 접근 방식은 ‘선점 형 멀티 태스킹’이라고합니다. 실행 스레드는 제어 전송과 관련이 없습니다. 스케줄러는 대신 실행 컨텍스트를 투명하게 전환합니다. 협력 적 접근 방식은 ‘그린 스레드’, 즉 1 : 1을 OS 스레드에 매핑하지 않는 논리적 동시 코 루틴을 구현하는 데 자주 사용됩니다. 이것이 Go 런타임과 해당 고 루틴이 구현되는 방식입니다.

최신 정보

GOMAXPROCS 환경 변수에 대해 언급했지만 그것이 무엇인지 설명하지 않았습니다. 이 문제를 해결할 때입니다.

이 변수가 양수로 설정 N되면 Go 런타임은 N모든 녹색 스레드가 예약되는 최대 기본 스레드 를 생성 할 수 있습니다. 네이티브 스레드는 운영 체제 (Windows 스레드, pthreads 등)에 의해 생성되는 일종의 스레드입니다. 즉 N, 값이 1보다 크면 고 루틴이 다른 네이티브 스레드에서 실행되도록 예약되어 결과적으로 병렬로 실행될 수 있습니다 (최소한 컴퓨터 기능까지 : 시스템이 멀티 코어 프로세서를 기반으로하는 경우). 이러한 스레드는 실제로 병렬 일 가능성이 높습니다. 프로세서에 단일 코어가있는 경우 OS 스레드에 구현 된 선점 형 멀티 태스킹은 병렬 실행의 가시성을 생성합니다.)

runtime.GOMAXPROCS()환경 변수를 미리 설정하는 대신 함수를 사용하여 GOMAXPROCS 변수를 설정할 수 있습니다. 현재 대신 프로그램에서 다음과 같이 사용하십시오 main.

func main() {
    runtime.GOMAXPROCS(2)
    go say("world")
    say("hello")
}

이 경우 흥미로운 결과를 관찰 할 수 있습니다. ‘hello’및 ‘world’줄이 고르지 않게 삽입 될 수 있습니다. 예 :

hello
hello
world
hello
world
world
...

이는 고 루틴이 OS 스레드를 분리하도록 예약 된 경우 발생할 수 있습니다. 이것은 사실 선점 형 멀티 태스킹이 작동하는 방식 (또는 멀티 코어 시스템의 경우 병렬 처리)입니다. 스레드는 병렬이고 결합 된 출력은 결정적이지 않습니다. BTW, 당신은 Gosched전화를 나가거나 제거 할 수 있습니다 .GOMAXPROCS가 1보다 크면 효과가없는 것 같습니다.

다음은 runtime.GOMAXPROCS호출로 프로그램을 여러 번 실행했을 때 얻은 것입니다 .

hyperplex /tmp % go run test.go
hello
hello
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world
hyperplex /tmp % go run test.go
hello
hello
hello
hello
hello
hyperplex /tmp % go run test.go
hello
world
hello
world
hello
world
hello
world
hello
world

때로는 출력이 예쁘고 때로는 그렇지 않습니다. 행동의 비결정론 🙂

또 다른 업데이트

최신 버전의 Go 컴파일러에서 Go 런타임은 고 루틴이 동시성 프리미티브 사용뿐만 아니라 OS 시스템 호출에서도 양보하도록 강제합니다. 이는 실행 컨텍스트가 IO 함수 호출에서도 고 루틴간에 전환 될 수 있음을 의미합니다. 결과적으로 최근의 Go 컴파일러에서는 GOMAXPROCS가 설정되지 않거나 1로 설정된 경우에도 비 결정적 동작을 관찰 할 수 있습니다.


답변

협업 일정이 범인입니다. 양보하지 않으면 다른 ( “세계”) 고 루틴은 합법적으로 메인이 종료되기 전 / 종료 될 때 실행할 기회를 얻지 못할 수 있습니다. 이는 사양에 따라 모든 고 루틴을 종료합니다. 전체 과정.


답변