채널로 Go 동시성 마스터하기
한때 Go로 이미지 처리 파이프라인을 구축한 적이 있습니다.
모든 단계에 goroutine을 사용했습니다. 채널로 이들을 연결했습니다. 그리고 실행 버튼을 눌렀습니다.
프로그램은 멈췄습니다. 메모리 사용량은 치솟았습니다. 그러더니 죽어버렸습니다. 에러는 없었습니다. 오직 정적만이 감돌았습니다.
고장 난 스케줄러를 디버깅하느라 몇 시간을 허비했습니다. 제 잘못이었습니다. 채널이 어떻게 작동하는지 이해하지 못했던 것입니다.
Go 프로그램이 멈추는 것을 방지하기 위한 세 가지 규칙을 소개합니다.
- Nil 채널은 블랙홀입니다 Nil 채널은 영원히 차단(block)됩니다. Nil 채널에 데이터를 보내거나 받으려고 하면, goroutine은 그 상태로 멈춰버립니다.
- 항상
make로 채널을 초기화하세요. - 확실하지 않다면 nil 체크를 사용하세요.
- 닫기(Closing) 규칙으로 패닉(panic) 방지하기 채널을 닫는 것은 되돌릴 수 없습니다.
- 오직 송신자(sender)만이 채널을 닫아야 합니다.
- 닫힌 채널에 데이터를 보내면 패닉이 발생합니다.
- 채널이 닫혔는지 확인하려면 두 번째 반환 값을 사용하세요:
v, ok := <-ch. ok가false라면 채널이 닫힌 것입니다.
- 안전을 위해 채널 방향(direction) 사용하기 Go에서는 채널이 송신용인지 수신용인지 지정할 수 있습니다.
chan<- int는 데이터를 보낼 수만 있음을 의미합니다.<-chan int는 데이터를 받을 수만 있음을 의미합니다.- 이를 통해 코드를 실행하기 전에 컴파일러가 실수를 잡아낼 수 있습니다.
깔끔한 파이프라인을 구축하는 방법:
defer close를 사용하여 채널이 정확히 한 번만 닫히도록 하세요.- 채널을 순회할 때는
range를 사용하세요. 채널이 닫히면 자동으로 중단됩니다. - 함수에 방향 타입(direction types)을 지정하여 엄격한 계약(contract)을 만드세요.
이러한 패턴을 따르면 리크(leak)와 패닉을 방지할 수 있습니다. 테스트하기 쉽고 견고한 시스템을 구축할 수 있습니다.
도전 과제:
여러 입력 채널을 하나의 출력 채널로 병합하는 함수를 작성해 보세요. select 루프를 사용하고, 500ms 타임아웃을 추가하세요. goroutine이 누수되지 않도록 보장해야 합니다.
댓글에 여러분의 솔루션을 공유해 주세요.