다음 칼럼에서는 golang 튜토리얼 칼럼에서 여러분에게 Golang 코루틴 스케줄링을 소개합니다. 도움이 필요한 친구들에게 도움이 되길 바랍니다!
1. 스레드 모델
- N:1 모델, N개의 사용자 공간 스레드는 1개의 커널 공간 스레드에서 실행됩니다. 장점은 컨텍스트 전환이 매우 빠르지만 멀티 코어 시스템을 활용할 수 없다는 것입니다.
- 1:1 모델, 1개의 커널 공간 스레드가 1개의 사용자 공간 스레드를 실행합니다. 이는 멀티 코어 시스템의 장점을 최대한 활용하지만 모든 일정이 사용자 모드와 커널 모드 사이를 전환하기 때문에 컨텍스트 전환이 매우 느립니다. (POSIX 스레드 모델(pthread), Java)
- M:N 모델에서 각 사용자 스레드는 여러 커널 공간 스레드에 해당하며 하나의 커널 공간 스레드도 여러 사용자 공간 스레드에 해당할 수 있습니다. Go는 이 모델을 채택하고 원하는 수의 커널 모델을 사용하여 원하는 수의 고루틴을 관리할 계획입니다. 이는 위의 두 모델의 장점을 결합하지만 스케줄링이 복잡하다는 단점이 있습니다.
golang의 코루틴 스케줄링을 살펴보겠습니다
- M: 커널 스레드에 해당하는 사용자 공간 스레드, posix pthread와 유사
- P: 실행 컨텍스트를 나타냅니다. 이는 이전 섹션에서 구현한 스케줄러입니다. , 스케줄러는 준비된 대기열에도 해당합니다.
- G: goroutine, 즉 코루틴
2. 스케줄링 모델 소개
Groutine은 GPM 스케줄링 모델을 통해 강력한 동시성 구현을 가질 수 있습니다. goroutine 스케줄링 모델을 설명하겠습니다. .
Go의 스케줄러 내부에는 M, P, G
M의 세 가지 중요한 구조가 있습니다. M은 커널 수준 스레드의 캡슐화이며 숫자는 실제 CPU 수에 해당합니다. 하나의 M은 스레드이고 고루틴입니다. M 위에서 실행 중입니다. M은 소형 객체 메모리 캐시(mcache), 현재 실행 중인 고루틴, 난수 생성기 등 많은 정보를 유지하는 대규모 구조입니다. G: 자체 스택, 명령이 있는 고루틴을 나타냅니다. 스케줄링을 위한 포인터 및 기타 정보(대기 채널 등).
P: P의 전체 이름은 프로세서입니다. 주요 목적은 고루틴을 실행하는 것입니다. 각 프로세서 객체에는 LRQ(로컬 실행 큐)가 있습니다. 할당되지 않은 고루틴 객체는 GRQ(글로벌 실행 큐)에 저장되어 LRQ의 특정 P에 할당되기를 기다리고 있습니다.
Golang은 멀티 스레딩 모델을 사용합니다. 더 자세히 말하면 2레벨 스레딩 모델이지만 시스템 스레드(커널 수준 스레드)를 캡슐화하고 사용자에게 경량 코루틴 고루틴(사용자 수준 스레드)을 노출합니다. 사용자 수준 스레드를 커널 수준 스레드로 예약하는 것은 golang의 런타임에 의해 처리되며 예약 논리는 외부 세계에 투명합니다. 고루틴의 장점은 완전한 사용자 상태에서 컨텍스트 전환이 수행된다는 점입니다. 스레드만큼 자주 사용자 상태와 커널 상태 사이를 전환할 필요가 없으므로 리소스 소비가 절약됩니다.
스케줄링 구현
위 그림에서 볼 수 있듯이 2개의 물리적 스레드 M이 있고, 각 M에는 프로세서 P가 있고, 각각 실행 중인 고루틴도 있습니다.
P 수는 실제로 실제 동시성, 즉 동시에 실행할 수 있는 고루틴 수를 나타내는 GOMAXPROCS()를 통해 설정할 수 있습니다.
사진 속 회색 고루틴은 실행되고 있지 않고, 예약 대기 중인 준비 상태입니다. P는 이 큐(runqueue라고 함)를 유지합니다. Go 언어에서는 go 함수를 시작하는 것이 쉽습니다. 따라서 go 문이 실행될 때마다 goroutine이 runqueue 큐의 끝에 추가되고 예약됩니다. 다음 클릭에서는 실행 대기열에서 고루틴을 꺼내고(어떤 고루틴을 취할지 결정하는 방법은 무엇입니까?) 실행합니다.
OS 스레드 M0이 차단되면(아래 참조) P는 대신 M1을 실행하고 있으며 그림의 M1이 생성되거나 스레드 캐시에서 제거될 수 있습니다.
MO가 반환되면 고루틴을 실행하기 위해 P를 얻으려고 시도해야 합니다. 일반적으로 다른 OS 스레드에서 P를 얻습니다.
얻지 못하면 고루틴 Put을 사용합니다. 그것은 글로벌에서
runqueue를 실행한 다음 자체적으로 절전 모드로 전환합니다(스레드 캐시에 넣음). 모든 P는 주기적으로 전역을 확인합니다.
runqueue를 실행하고 그 안에 있는 고루틴을 실행하세요. 그렇지 않으면 전역 runqueue의 고루틴이 절대 실행되지 않습니다.
또 다른 상황은 P가 할당한 작업 G가 빠르게 완료되어(불균일 분포) 프로세서 P가 매우 바쁜 상태가 되지만 전역인 경우 다른 P에는 여전히 작업이 있다는 것입니다.
실행 대기열에는 작업 G가 없으므로 P는 실행하려면 다른 P로부터 일부 G를 가져와야 합니다. 일반적으로 P가 다른 P로부터 작업을 가져오려면 일반적으로 실행이 필요합니다.
아래와 같이 각 OS 스레드를 완전히 사용할 수 있도록 보장하는 대기열의 절반입니다.
3. GPM 생성 관련 문제
M과 P의 수는 어떻게 결정하나요? 아니면 M과 P는 언제 생성되나요?
1. P의 수:
- 는 시작 시 환경 변수 $GOMAXPROCS 또는 런타임 메소드 GOMAXPROCS()에 의해 결정됩니다(기본값은 1). 이는 프로그램 실행 중 언제든지 $GOMAXPROCS 고루틴만 동시에 실행된다는 것을 의미합니다.
2. M 개수:
- go 언어 자체의 제한 사항: go 프로그램이 시작되면 최대 M 개수가 설정되며 기본값은 10000입니다. 그러나 커널에서 지원하기 어렵습니다. 스레드 수가 너무 많기 때문에 이 제한은 무시할 수 있습니다.
- 런타임/디버그의 SetMaxThreads 함수는 M의 최대 수를 설정합니다.
- 하나의 M이 차단되면 새로운 M이 생성됩니다.
M은 P 개수와 절대적인 관계가 없습니다. 하나의 M이 차단되면 P는 다른 M을 생성하거나 전환합니다. 따라서 P의 기본 개수가 1이더라도 다수의 M이 생성될 수 있습니다.
3. P가 생성될 때: P의 최대 개수 n을 결정한 후 런타임 시스템은 이 개수를 기준으로 n개의 P를 생성합니다.
4. M이 생성될 때: P와 연결하고 실행 가능한 G를 실행할 M이 충분하지 않습니다. 예를 들어, 이때 모든 M이 차단되고 P에 아직 준비된 작업이 많이 있으면 유휴 M을 찾습니다. 유휴 M이 없으면 새 M이 생성됩니다.
어떤 P협회를 선택해야 할까요?
P와 M의 관계는 언제 바뀌나요?
M이 시스템 호출로 인해 차단되면(M에서 실행 중인 G가 시스템 호출에 들어갈 때) M과 P가 분리됩니다. 이때 P의 준비 대기열에 작업이 있으면
P는 유휴 M을 연결합니다. 또는 연결을 위해 M을 만듭니다. (즉, Go는 libtask처럼 IO 차단을 처리하지 않습니까? 확실하지 않습니다.)
Ready G는 어떤 P의 Ready 대기열에 들어갈지 어떻게 선택합니까?
- 기본적으로 P의 기본 개수는 1(M은 반드시 1일 필요는 없음)이므로 GOMAXPROCS를 변경하지 않으면 프로그램에서 go 문을 사용하여 얼마나 많은 고루틴을 생성하더라도 해당 고루틴은 동일한 AP의 준비 대기열.
- P가 여러 개 있는 경우: GOMAXPROCS가 수정되거나 Runtime.GOMAXPROCS가 호출되는 경우 런타임 시스템은 각 P의 준비 대기열에 있는 모든 G를 균등하게 배포합니다.
각 P의 준비 대기열에 G가 있는지 확인하는 방법
P의 준비 대기열에 있는 모든 작업이 실행되면 P는 다른 P의 준비 대기열 중 일부를 자신의 준비 대기열로 가져오려고 시도합니다. 각 P의 준비 대기열에는 실행될 수 있는 작업이 있습니다.
위 내용은 Golang 코루틴 스케줄링 정보의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!