goroutine은 Go 런타임이 관리하는 가볍고 동시 실행되는 함수입니다. 하나를 실행하는 것은 함수 호출 앞에 go를 붙이는 것만큼 간단합니다. goroutine은 Go의 대표적인 동시성 기능으로, OS 스레드보다 훨씬 저렴하여 수천, 수백만 개도 실행할 수 있습니다.
goroutine 실행
{
fmt.Println()
}
sayHello()
{
fmt.Println()
}()
go 키워드는 함수를 새 goroutine에서 시작하고 즉시 반환합니다 — 호출 코드는 기다리지 않고 계속됩니다.
OS 스레드: 각 약 1~2 MB 메모리 → 최대 수천 개
goroutine: 약 2 KB 초기 스택(필요에 따라 증가) → 수백만 개 실현 가능
Go 런타임이 많은 goroutine을 적은 OS 스레드 풀에 스케줄링(M:N).
goroutine은 OS 스레드가 아닙니다 — Go 스케줄러가 많은 goroutine을 적은 OS 스레드에 다중화합니다. 이것이 goroutine을 매우 저렴하게 만들어, 대규모 동시성(예: 수백만 연결을 처리하는 서버에서 연결당 하나의 goroutine)을 가능하게 합니다.
func main() {
go sayHello() // ❌ main이 이 goroutine 실행 전에 종료될 수 있음 → 아무것도 출력 안 됨
// 프로그램이 즉시 끝남
}
main이 반환되면 프로그램이 끝납니다 — 아직 실행 중인 goroutine을 죽입니다. 조율이 필요합니다.
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1) // 카운터 증가
go func(n int) {
defer wg.Done() // 이 goroutine이 끝나면 감소
fmt.Println("worker", n)
}(i) // i를 인자로 전달(루프 변수 캡처 버그 방지)
}
wg.Wait() // 모든 goroutine이 Done()을 호출할 때까지 블록
sync.WaitGroup은 goroutine 집합이 완료되기를 기다리는 표준 방법입니다.
ch := make(chan string)
go func() { ch <- "result" }() // channel로 결과 전송
msg := <-ch // 수신(값이 도착할 때까지 블록)
Go 철학: "메모리를 공유하여 통신하지 말고, 통신하여 메모리를 공유하라" — 공유 변수 + 잠금 대신 channel을 써서 goroutine 간 데이터를 전달하세요.
goroutine은 Go의 정의적 기능이며, 동시적·네트워크 시스템에 탁월한 주된 이유입니다.
극단적인 가벼움(스레드는 메가바이트인데 킬로바이트, OS 스레드에 M:N으로 스케줄링)은 대규모 동시성을 실용적이고 쉽게 만듭니다 — 서버가 자원 걱정 없이 요청이나 연결당 goroutine을 생성할 수 있는데, OS 스레드로는 어렵거나 불가능합니다.
실행 방법(go), 동기화의 결정적 필요성(main 종료가 goroutine을 죽임; WaitGroup으로 대기), 그리고 공유 메모리보다 channel을 통한 통신을 선호하는 Go의 방식을 이해하는 것은 올바른 동시 Go 작성에 기본입니다.
goroutine(과 그 주변 패턴)은 Go의 가치 제안의 핵심이며, 언어에서 가장 중요하고 자주 출제되는 주제 중 하나입니다.