고루틴은 Go 런타임에 의해 관리되는 경량 실행 스레드입니다. 새로운 고루틴을 시작하려면 함수 호출 앞에 go 키워드를 사용합니다. 내부적으로는 스택과 작업 상태를 설명하는 구조체가 생성되어 고루틴 스케줄러의 큐에 추가됩니다.
운영 체제의 스레드와 달리, 고루틴은 훨씬 더 작은 초기 스택(보통 2KB)을 가지며 필요에 따라 스택이 자동으로 확장됩니다. Go의 스케줄러는 이들을 운영 체제에서 사용할 수 있는 스레드에 알아서 분배합니다(M:N 모델).
스레드와의 주요 차이점은 다음과 같습니다:
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
Go의 고루틴은 여러 코어에서 병렬로 실행될 수 있습니까?
종종 잘못된 답변이 있습니다: "아니요, Go는 그린 스레드를 사용하므로". 실제로는 환경 변수를 사용하거나 runtime.GOMAXPROCS(n)를 호출함으로써 Go는 고루틴의 실행을 모든 사용 가능한 프로세서 코어에서 병렬적으로 수행할 수 있습니다.
import "runtime" func main() { runtime.GOMAXPROCS(4) // 4개의 코어를 사용하도록 허용 ... }
이야기
Go에서 백엔드 서비스 프로젝트에서 고루틴을 통한 작업자 풀을 구현했지만, 프로그래머들이 동일하게 작동하는 고루틴의 수를 제한하는 것을 잊었습니다. 그 결과, 부하가 증가하자 애플리케이션은 수천 개의 고루틴을 시작하여 메모리가 고갈되고 서비스가 중단되었습니다. 활성 고루틴의 제한을 도입하여 문제를 해결했습니다(예: 세마포어나 작업자 풀 사용).
이야기
한 직원이 뮤텍스나 채널 없이 일반 전역 변수를 사용하여 고루틴 간 데이터 동기화를 잘못 구현했습니다. 이로 인해 데이터 경쟁이 발생하여 결제 처리 중에 오류가 간헐적으로 발생했습니다. 문제는 production에서 시작한 후에야 발견되었습니다.
이야기
파싱 서비스에서 select에 nil 채널을 전달하는 것을 놓쳤습니다. 채널을 닫은 후 select가 데이터를 기다리면서 멈춰버렸고 일부 고루틴이 "중단"되었습니다. nil을 닫힌 채널에 할당하고 select를 올바르게 처리하여 문제를 해결했습니다.