backend

Go 언어 select 문 정리: 여러 channel을 동시에 다루는 방법

mirabo01 2026. 1. 18. 22:07

channel까지 이해했다면, 이제 Go 동시성의 흐름을 실제로 제어하는
select 문을 살펴볼 차례다.

goroutine과 channel을 사용하다 보면
“여러 channel 중 하나라도 준비되면 처리하고 싶다”는 상황이 자연스럽게 등장한다.
이때 사용하는 문법이 바로 select다.

이 글에서는

  • select 문이 어떤 역할을 하는지
  • 동작 방식과 기본 규칙
  • 실무에서 자주 사용하는 패턴

을 중심으로 정리한다.


select 문이 필요한 이유

channel 하나만 다룰 때는 <-ch로 충분하다.
하지만 channel이 여러 개라면 이야기가 달라진다.

// 이런 상황은 불가능하다
if <-ch1 {
} else if <-ch2 {
}

여러 channel을 동시에 기다리기 위해
Go는 select라는 전용 문법을 제공한다.


select 기본 구조

select {
case v := <-ch1:
    fmt.Println("ch1:", v)
case v := <-ch2:
    fmt.Println("ch2:", v)
}
  • 준비된 case 하나만 실행
  • 실행 후 select 블록 종료
  • break를 쓰지 않아도 된다

구조는 switch와 비슷하지만,
조건이 아니라 channel 통신을 기준으로 분기한다는 점이 다르다.

[이미지: Go select 기본 구조 다이어그램]


select의 동작 규칙

select는 다음 규칙을 따른다.

  1. 준비된(case 실행 가능한) channel이 있는지 검사
  2. 하나라도 있으면 그중 하나를 랜덤하게 선택
  3. 준비된 case가 없으면 대기(blocking)

⚠️ 중요한 포인트
여러 case가 동시에 준비돼 있어도
실행 순서는 보장되지 않는다.

이 특성 덕분에 특정 channel이 계속 우선 처리되는 현상을 피할 수 있다.


default가 있는 select

select {
case v := <-ch:
    fmt.Println(v)
default:
    fmt.Println("no data")
}
  • 준비된 channel이 없으면 default 실행
  • 이 경우 select는 blocking되지 않는다

이 패턴은 다음 상황에서 자주 사용된다.

  • non-blocking receive
  • 상태 체크용 로직
  • 간단한 폴링 처리

[이미지: select with default 흐름]


select + time.After 패턴

일정 시간 동안 channel을 기다리고 싶을 때는
time.After와 select를 함께 사용한다.

select {
case v := <-ch:
    fmt.Println(v)
case <-time.After(time.Second):
    fmt.Println("timeout")
}
  • 지정한 시간이 지나면 channel이 자동으로 신호를 보낸다
  • 타임아웃 처리에 매우 자주 쓰이는 패턴

실무에서 네트워크, 외부 API, 워커 처리 코드에 거의 빠지지 않는다.


channel 종료 감지와 select

select {
case v, ok := <-ch:
    if !ok {
        fmt.Println("channel closed")
        return
    }
    fmt.Println(v)
}
  • channel 종료 여부를 select 안에서도 처리 가능
  • range와 select를 조합해 사용하는 경우도 많다

이 방식은
여러 입력 channel 중 하나라도 종료되었을 때
흐름을 제어해야 하는 상황에서 유용하다.


select는 반복문과 함께 쓰인다

select는 단독으로 쓰이기보다
대부분 for와 함께 사용된다.

for {
    select {
    case job := <-jobs:
        process(job)
    case <-done:
        return
    }
}
  • 이벤트 루프 형태
  • 워커, 서버, 데몬 코드에서 흔히 등장

이 구조가 익숙해지면,
Go로 작성된 서버 코드의 큰 흐름이 보이기 시작한다.

[이미지: select 기반 이벤트 루프 구조]


select 사용 시 주의할 점

⚠️ 주의사항

  • select 자체는 goroutine을 종료시키지 않는다
  • default를 남용하면 busy loop가 될 수 있다
  • channel 설계가 복잡하면 select도 읽기 어려워진다

select는 강력하지만,
channel 개수가 많아질수록 설계 난이도도 함께 올라간다.


정리

select는
여러 channel 사이의 우선순위 없는 분기를 표현하는 도구다.

  • 준비된 channel 중 하나를 선택
  • default로 non-blocking 처리 가능
  • time.After와 함께 쓰면 타임아웃 구현 가능

goroutine, channel, select까지 이해하면
Go 동시성의 기본 도구들은 거의 다 익힌 셈이다.

다음 글에서는 동시성 정리의 마지막 단계로
sync 패키지 (WaitGroup, Mutex) 를 다루면 흐름이 자연스럽다.