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는 다음 규칙을 따른다.
- 준비된(case 실행 가능한) channel이 있는지 검사
- 하나라도 있으면 그중 하나를 랜덤하게 선택
- 준비된 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) 를 다루면 흐름이 자연스럽다.
'backend' 카테고리의 다른 글
| Go 모듈(go mod)과 의존성 관리: 실무에서 헷갈리지 않는 기준 정리 (0) | 2026.01.20 |
|---|---|
| Go 프로젝트 구조와 패키지 설계: 실무에서 흔히 쓰는 기준 정리 (0) | 2026.01.19 |
| Go 언어 channel 기초: goroutine 간 통신과 동기화 방법 (1) | 2026.01.17 |
| Go 언어 goroutine과 동시성 기초: 병렬 처리의 기본 단위 이해하기 (0) | 2026.01.16 |
| Go 언어 에러 처리 방식 정리: error 인터페이스와 실전 패턴 (0) | 2026.01.15 |