문제의 역사: 채널은 Go의 CSP 모델에서의 기본 개념 중 하나입니다. 고루틴 간 데이터 전송을 위해 설계되었습니다. 채널 작업은 데드락, 메모리 누수 및 패닉을 피하기 위해 특별한 주의가 필요합니다.
문제: 채널을 닫고 이후에 사용하는 것은 종종 패닉(panic: send on closed channel)을 초래합니다. nil 채널로 작업하거나 닫힌 채널에서 확인 없이 읽으려 하면 예기치 않은 결과를 초래할 수 있습니다. 서로 다른 고루틴이 동시에 한 채널에 읽고 쓸 수 있으므로, 명확하게 설명된 생명 주기 논리가 필요합니다.
해결책: 채널은 그쪽에서 아무도 더 이상 쓰지 않을 때만 닫혀야 합니다 (일반적으로 생산자). 채널이 닫힌 후에는 고갈될 때까지 읽을 수 있으나 쓸 수는 없습니다 — 패닉이 발생합니다. 닫힘을 확인하는 것은 채널에서 반환하는 두 번째 매개변수를 통해 쉽게 할 수 있습니다:
코드 예:
ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i } close(ch) }() for v := range ch { fmt.Println(v) }
주요 특징:
1. 한 고루틴만 채널을 읽고 추가값을 기다리지 않을 때 채널을 닫아야 하나요?
답변: 아무도 range를 통해 닫힘을 기다리지 않는다면 채널을 닫을 필요는 없습니다. 그러나 for v := range ch를 사용할 경우에는 반드시 채널을 닫아야 하며, 그렇지 않으면 루프가 종료되지 않습니다.
2. 닫힌 채널에서 읽으면 어떻게 되나요?
답변: 버퍼가 고갈될 때까지 값이 반환되며, 그 후에는 zero value와 false가 반환됩니다. 닫힌 후에도 안전하게 채널을 끝까지 읽을 수 있습니다.
ch := make(chan int, 1) ch <- 42 close(ch) v, ok := <-ch // v = 42, ok = true v, ok = <-ch // v = 0, ok = false
3. 누가 채널을 닫아야 하나요: 읽는 쪽인가, 쓰는 쪽인가?
답변: 항상 "쓰는 쪽" (값을 보내고 작업이 끝났을 때 아는 쪽)이 닫아야 합니다. "읽는 쪽"은 경쟁 조건(race condition)을 방지하기 위해 채널을 닫아서는 안 됩니다.
애플리케이션에서 여러 고루틴이 한 채널에 쓰고 하나는 읽습니다. 누군가 읽는 쪽에서 채널을 닫았고 다른 고루틴이 여전히 보내려고 할 때 — 패닉이 발생합니다.
장점: 조건적으로 단순한 상호작용 논리. 단점: 닫힘 시점을 예측할 수 없고, 패닉, 경쟁 조건(race condition)이 발생합니다.
쓴이는 모든 고루틴을 관리하고 sync.WaitGroup을 사용하여 모든 쓰기가 완료된 후에만 채널을 닫습니다. 읽는 쪽은 채널이 닫힌 후 올바르게 range 루프를 종료하며 안전하게 오류를 처리합니다.
장점: 올바른 동기화, 메모리 누수와 데드락 없음, 예측 가능한 동작. 단점: 로직이 약간 복잡해지고 모든 작업을 명시적으로 종료해야 합니다.