Go의 비밀스러운 삶: select 구문
Source: Dev.to
번역하려는 전체 텍스트를 제공해 주시면, 요청하신 대로 소스 링크는 그대로 두고 내용만 한국어로 번역해 드리겠습니다.
Source: …
빠른 데이터가 느린 채널을 기다리지 않게 하는 방법
Part 25: 멀티플렉서, 타임아웃, 그리고 논블로킹 읽기
Ethan은 터미널 출력이 한 줄씩 흘러나오는 것을 지켜보고 있었다. 그 속도는 고통스러울 정도로 느렸다.
“이해가 안 돼요,” 라고 그는 눈을 비비며 말했다. “두 개의 고루틴이 데이터를 보내고 있어요. 하나는 1밀리초 안에 반환되는 로컬 캐시이고, 다른 하나는 5초가 걸리는 네트워크 호출이에요. 그런데 빠른 데이터가 느린 데이터를 기다리고 있네요.”
Eleanor가 다가와 그의 코드를 살펴보았다.
문제 코드
func process(cacheChan “You have created a traffic jam,” Eleanor observed. “Channel receives are blocking. Because you asked for `netChan` first, your function halts right there. It doesn’t matter that `cacheChan` has been ready for 4.99 seconds. You are forcing a sequential read on concurrent data.”
어떤 채널이 먼저 준비되는지 어떻게 읽을 수 있나요? Ethan이 물었다.
멀티플렉서가 필요합니다. Go에서는 select 문을 사용합니다.
select 문
Eleanor는 그의 함수를 다시 작성했다. select 문은 switch와 정확히 같은 형태이지만, 채널 연산에만 작동합니다.
해결책
func process(cacheChan “Exactly,” Eleanor said. “`select` listens to all its cases simultaneously. Whichever channel is ready first, it executes that case. If multiple channels are ready at the same time, Go picks one completely at random to ensure fairness.”
타임아웃 (time.After)
Ethan은 네트워크가 끊겨 netChan이 아무것도 보내지 않을 경우를 궁금해했다.
“현재 상태에서는,” Eleanor가 답했다, “
select가 영원히 기다리게 됩니다. 이는 고루틴 누수입니다. 실제 코드에서는 타임아웃을 강제해야 합니다.”
타임아웃 추가
func processWithTimeout(cacheChan “`time.After` acts as a ticking time bomb,” Eleanor explained. “If `netChan` or `cacheChan` don’t respond within two seconds, the timeout case wins the race.”
안전 주의사항: time.After는 타이머가 발동할 때까지 메모리를 차지합니다. 빠르고 짧은 루프 안에 넣으면 메모리 누수가 발생합니다. 이런 경우에는 time.NewTimer를 사용하고 명시적으로 Stop()하십시오. 더 복잡하고 다계층적인 타임아웃이 필요하면 context.WithTimeout을 사용하는 것이 좋습니다(에피소드 22 참고).
논블로킹 읽기 (default)
“전혀 기다리고 싶지 않다면 어떻게 하나요? 채널을 살짝 들여다보고, 데이터가 있으면 가져가고, 없으면 바로 다른 일을 하고 싶어요,” Ethan이 물었다.
“그럴 땐
default케이스를 사용합니다,” Eleanor가 미소 지으며 답했다.
예시
func checkStatus(statusChan “A `select` with a `default` case is completely non‑blocking,” she explained. “If no channels are ready that exact microsecond, it falls through to the `default` block immediately.”
Ethan은 몸을 뒤로 젖혔다. “그럼 여러 일을 동시에 기다릴 수 있고, 시간 제한도 설정하고, 전혀 기다리지 않을 수도 있군요.”
“정확합니다,” Eleanor가 말했다. “이제 고루틴에 휘둘리지 않고, 직접 조율할 수 있습니다.”
핵심 개념
select 문
- 고루틴이 여러 통신 연산을 기다릴 수 있게 하는 제어 구조입니다.
- 하나의 케이스가 실행될 수 있을 때까지 차단됩니다.
- 여러 케이스가 준비되면 무작위로 하나를 선택합니다.
빈 select
- 케이스가 없는
select {}는 현재 고루틴을 영원히 차단합니다.
타임아웃 및 메모리
time.After(duration)은 간단하고 일회성 타임아웃에 적합합니다.- 프로덕션 경고: 빈번한 루프에서는
time.NewTimer(duration)을 사용하고.Stop()을 호출해 메모리 누수를 방지하세요. - 복잡하고 다계층적인 타임아웃에는
context.WithTimeout을 사용하는 것이 좋습니다.
루프 라벨
select내부에서for루프를 빠져나가려면 라벨을 사용해야 합니다(예:break outer). 일반break는select만 종료합니다.
논블로킹 연산 (default)
default케이스를 추가하면select가 논블로킹이 됩니다. 다른 채널이 준비되지 않으면 즉시 실행됩니다.
다음 에피소드: 워커 풀 – Ethan이 수천 개의 작업을 동시에 처리하는 방법을 배웁니다.
- 메모리 고갈을 방지하기 위해 고정된 수의 고루틴을 사용하는 작업.
- Aaron Rose는 tech-reader.blog의 소프트웨어 엔지니어이자 기술 작가이며, Think Like a Genius의 저자입니다.