Channel — goroutine 사이의 파이프라인
Ruby에는 없는 개념. goroutine 간 데이터를 안전하게 주고받는 통로.
Ruby에서 스레드 간 데이터를 주고받으려면 Queue를 쓴다: queue = Queue.new; Thread.new { queue.push(42) }; val = queue.pop.
Go의 channel도 비슷하다. 하지만 언어 문법에 내장되어 있다:
ch := make(chan int)
go func() { ch <- 42 }() // 보내기
val := <-ch // 받기
<- 연산자가 핵심이다. ch <- 42는 채널에 값을 보내고, <-ch는 채널에서 값을 받는다. Ruby의 Queue#push/pop과 같은 역할인데, 문법이 간결하다.
버퍼 채널 vs 언버퍼 채널
make(chan int) — 언버퍼. 보내는 쪽이 받는 쪽이 준비될 때까지 블로킹된다. 동기적 핸드오프.
make(chan int, 10) — 버퍼 10. 버퍼가 가득 찰 때까지 보내는 쪽이 블로킹되지 않는다. Ruby의 SizedQueue.new(10)과 비슷.
select — 여러 채널을 동시에 대기
select {
case msg := <-ch1:
fmt.Println(msg)
case msg := <-ch2:
fmt.Println(msg)
case <-time.After(5 * time.Second):
fmt.Println("timeout")
}
여러 채널 중 먼저 값이 오는 쪽을 처리한다. Ruby에는 이런 게 없다. IO.select가 소켓에서 비슷한 역할을 하지만, 범용 채널 멀티플렉싱은 Go만의 영역이다.
파이프라인 패턴
channel을 연결하면 Unix 파이프라인처럼 데이터 처리 파이프라인을 만들 수 있다:
생성 → 필터 → 변환 → 출력
각 단계가 goroutine으로 동시 실행된다. Ruby에서 array.select.map.each 체이닝을 하듯이, Go에서는 channel을 연결해서 스트림 처리를 한다.
Ruby에서 Go로
Ruby: Queue.new / push / pop → Go: make(chan T) / ch <- val / <-ch
Ruby: SizedQueue.new(n) → Go: make(chan T, n) (버퍼 채널)
Ruby: 해당 없음 → Go: select { case } (여러 채널 동시 대기)
Ruby: .select.map.each 체이닝 → Go: channel 연결 파이프라인
장점
- ✓ 언어 레벨에서 지원 — 별도 라이브러리 없이 동시성 패턴 구현 가능
- ✓ select로 타임아웃, 취소, 멀티플렉싱을 간결하게 표현
단점
- ✗ 채널 방향을 잘못 쓰면 데드락 — 디버깅이 까다롭다
- ✗ Ruby의 Enumerable 체이닝에 비해 파이프라인 코드가 장황하다