💻 프로그래밍

고루틴

Goroutine

Go 언어의 경량 스레드. OS 스레드보다 적은 메모리로 수천 개를 동시 실행 가능. go 키워드로 생성.

📖 상세 설명

고루틴(Goroutine)은 Go 언어에서 제공하는 경량 스레드로, 동시성 프로그래밍의 핵심 요소입니다. OS 스레드가 보통 1MB 이상의 스택을 사용하는 반면, 고루틴은 약 2KB의 스택으로 시작하여 필요에 따라 동적으로 확장됩니다.

고루틴은 go 키워드와 함께 함수 호출을 작성하면 생성됩니다. Go 런타임의 스케줄러가 M:N 스케줄링(M개의 고루틴을 N개의 OS 스레드에 매핑)을 수행하여, 수천에서 수백만 개의 고루틴을 효율적으로 관리합니다.

고루틴 간 통신은 채널(Channel)을 통해 이루어집니다. "공유 메모리로 통신하지 말고, 통신으로 메모리를 공유하라"는 Go의 철학에 따라, 채널을 사용하면 락 없이도 안전한 동시성 코드를 작성할 수 있습니다.

고루틴은 생성 비용이 매우 낮아 서버의 각 요청마다 새 고루틴을 생성하는 패턴이 일반적입니다. 다만 고루틴 누수(leaky goroutine)에 주의해야 하며, context를 사용해 적절히 종료시키는 것이 중요합니다.

💻 코드 예제

Go
package main

import (
    "fmt"
    "sync"
    "time"
)

// 기본 고루틴 생성
func sayHello(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

func main() {
    // go 키워드로 고루틴 생성
    go sayHello("World")

    // 채널을 통한 고루틴 간 통신
    ch := make(chan int, 3)

    // 생산자 고루틴
    go func() {
        for i := 1; i <= 3; i++ {
            ch <- i
            fmt.Printf("Sent: %d\n", i)
        }
        close(ch)
    }()

    // 소비자 (메인 고루틴)
    for num := range ch {
        fmt.Printf("Received: %d\n", num)
    }

    // WaitGroup으로 여러 고루틴 동기화
    var wg sync.WaitGroup

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Worker %d done\n", id)
        }(i)
    }

    wg.Wait() // 모든 고루틴 완료 대기
    fmt.Println("All workers completed")
}

🗣️ 실무 대화 예시

백엔드 개발자
"API 서버에서 외부 서비스 3개를 호출하는데 순차적으로 하면 응답이 느려요. 고루틴으로 병렬 처리하면 어떨까요?"
시니어 개발자
"좋은 생각이에요. errgroup 패키지를 쓰면 병렬 호출하면서 에러 처리도 깔끔하게 할 수 있어요."
백엔드 개발자
"context로 타임아웃도 설정하면 하나가 느려도 전체 요청이 블록되지 않겠네요."
면접관
"고루틴과 OS 스레드의 차이점을 설명해주세요."
지원자
"고루틴은 Go 런타임이 관리하는 경량 스레드입니다. 스택이 작고 동적으로 확장되며, 컨텍스트 스위칭 비용이 OS 스레드보다 훨씬 낮습니다. M:N 스케줄링으로 적은 OS 스레드에 많은 고루틴을 매핑합니다."
면접관
"고루틴 누수란 무엇이고 어떻게 방지하나요?"
지원자
"채널에서 대기 중인 고루틴이 영원히 깨어나지 못하면 누수가 발생합니다. context.WithCancel이나 context.WithTimeout으로 취소 시그널을 전파하고, select문에서 ctx.Done()을 처리해야 합니다."
리뷰어
"이 고루틴에서 루프 변수를 직접 캡처하고 있어요. Go 1.22 이전에서는 클로저 버그가 발생할 수 있습니다."
작성자
"아, 맞아요. 파라미터로 전달하거나 루프 내에서 새 변수에 할당하도록 수정하겠습니다."
리뷰어
"그리고 채널을 close하지 않으면 range로 읽는 쪽이 영원히 블록됩니다. 송신 완료 후 close 호출 추가해주세요."

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기