콘텐츠로 이동

스케줄러

스케줄러는 작업 스틸링 설계를 사용하여 프로세스를 실행합니다. 워커는 로컬 데크를 유지하고 유휴 상태일 때 서로에게서 스틸링합니다.

스케줄러는 Process 인터페이스를 구현하는 모든 타입과 작동합니다:

type Process interface {
Init(ctx context.Context, method string, input payload.Payloads) error
Step(events []Event, out *StepOutput) error
Close()
}
메서드목적
Init엔트리 메서드 이름과 입력 인자로 프로세스 준비
Step들어오는 이벤트로 상태 머신 진행, 출력에 yield 쓰기
Close리소스 해제

Initmethod 파라미터는 어떤 진입점을 호출할지 지정합니다. 프로세스 인스턴스는 여러 진입점을 노출할 수 있고, 호출자가 어떤 것을 실행할지 선택합니다. 이는 스케줄러가 프로세스를 올바르게 시작하고 있는지 검증하는 역할도 합니다.

스케줄러는 Step()을 반복적으로 호출하여 이벤트(yield 완료, 메시지)를 전달하고 yield(디스패치할 명령)를 수집합니다. 프로세스는 상태와 yield를 StepOutput 버퍼에 씁니다.

type Event struct {
Type EventType // EventYieldComplete 또는 EventMessage
Tag uint64 // yield 완료를 위한 상관 태그
Data any // 결과 데이터 또는 메시지 페이로드
Error error // yield 실패 시 에러
}

스케줄러는 기본적으로 GOMAXPROCS 워커를 스폰합니다. 각 워커는 캐시 친화적 LIFO 접근을 위한 로컬 데크를 가집니다. 글로벌 FIFO 큐는 새 제출과 교차 워커 전송을 처리합니다. 프로세스는 메시지 라우팅을 위해 PID로 추적됩니다.

flowchart TD
W[워커가 작업 필요] --> L{로컬 데크?}
L -->|항목 있음| LP[아래에서 LIFO 팝]
L -->|비어있음| G{글로벌 큐?}
G -->|항목 있음| GP[팝 + 최대 16개 배치 전송]
G -->|비어있음| S[무작위 피해자에서 스틸]
S --> SH[피해자 데크에서 절반 스틸]

워커는 우선순위 순서로 소스를 확인합니다:

우선순위소스패턴
1로컬 데크LIFO 팝, 락 프리, 캐시 친화적
2글로벌 큐배치 전송과 함께 FIFO 팝
3다른 워커피해자 데크에서 절반 스틸

글로벌에서 팝할 때 워커는 하나를 가져가고 최대 16개를 로컬 데크로 배치 전송합니다.

각 워커는 Chase-Lev 작업 스틸링 데크를 소유합니다:

type Deque struct {
buffer atomic.Pointer[dequeBuffer]
top atomic.Int64 // 스틸러가 여기서 스틸 (CAS)
bottom atomic.Int64 // 소유자가 여기서 푸시/팝
}

소유자는 동기화 없이 아래에서 푸시하고 팝합니다(LIFO). 스틸러는 CAS를 사용하여 위에서 스틸합니다(FIFO). 이를 통해 소유자는 최근 푸시된 항목에 캐시 친화적으로 접근하면서 오래된 작업을 스틸러에게 분배합니다.

StealHalfInto는 하나의 CAS 작업으로 항목의 절반을 가져가 경합을 줄입니다.

컨디션 변수에서 블로킹하기 전에 워커는 적응적으로 스핀합니다:

스핀 횟수액션
< 4타이트 루프
4-15스레드 양보 (runtime.Gosched)
>= 16컨디션 변수에서 블록
stateDiagram-v2
[*] --> Ready: 제출
Ready --> Running: 워커가 CAS
Running --> Complete: 완료
Running --> Blocked: 명령 yield
Running --> Idle: 메시지 대기
Blocked --> Ready: CompleteYield
Idle --> Ready: Send 도착
상태설명
Ready실행 대기 중
Running워커가 Step() 실행 중
Blockedyield 완료 대기 중
Idle메시지 대기 중
Complete실행 완료

웨이크업 플래그가 레이스를 처리합니다: 핸들러가 워커가 여전히 프로세스를 소유하는 동안(Running) CompleteYield를 호출하면 플래그를 설정합니다. 워커는 디스패치 후 플래그를 확인하고 설정되면 다시 큐에 넣습니다.

각 프로세스는 MPSC(다중 생산자, 단일 소비자) 이벤트 큐를 가집니다:

  • 생산자: 명령 핸들러(CompleteYield), 메시지 발신자(Send)
  • 소비자: 워커가 Step()에서 이벤트 드레인

스케줄러는 프로세스로 메시지를 라우팅하기 위해 relay.Receiver를 구현합니다. Send()가 호출되면 byPID 맵에서 대상 PID를 조회하고, 메시지를 이벤트로 프로세스 큐에 푸시하고, 유휴 상태면 글로벌 큐에 푸시하여 프로세스를 깨웁니다.

셧다운 시 스케줄러는 실행 중인 모든 프로세스에 취소 이벤트를 보내고 완료하거나 타임아웃될 때까지 기다립니다. 워커는 작업이 더 이상 없으면 종료합니다.