버퍼 채널
용량, 채널 닫기 및 채널에 대한 range
버퍼 채널은(는) CoddyKit의 무료 Go Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Go Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Go Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
버퍼가 있는 채널이란 무엇인가요
버퍼가 있는 채널에는 용량이 있으므로 버퍼에 여유 공간이 있는 동안에는 송신이 차단되지 않습니다. 채널은 대기열처럼 동작합니다:
package main
import "fmt"
func main() {
ch := make(chan int, 3) // capacity 3
ch <- 1 // does not block — buffer has room
ch <- 2 // does not block
ch <- 3 // does not block
// ch <- 4 // would block — buffer full
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}대기열로서의 버퍼가 있는 채널
버퍼가 있는 채널은 goroutine 사이에 FIFO 대기열을 구현합니다:
package main
import ("fmt"; "sync")
func producer(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= 5; i++ {
ch <- i
fmt.Printf("produced %d\n", i)
}
}
func main() {
ch := make(chan int, 2) // buffer 2 — producer runs ahead
var wg sync.WaitGroup
wg.Add(1)
go producer(ch, &wg)
wg.Wait()
close(ch)
for v := range ch { fmt.Printf("consumed %d\n", v) }
}채널에서 len()과 cap() 사용하기
버퍼가 있는 채널의 현재 크기와 용량을 확인합니다:
package main
import "fmt"
func main() {
ch := make(chan string, 5)
ch <- "a"
ch <- "b"
ch <- "c"
fmt.Println("cap:", cap(ch)) // 5
fmt.Println("len:", len(ch)) // 3 — items currently buffered
<-ch
fmt.Println("len:", len(ch)) // 2
}생산자와 소비자 분리
버퍼는 producer의 속도와 consumer의 속도를 분리합니다. producer는 차단되기 전에 버퍼 크기만큼 앞서 실행할 수 있습니다:
package main
import ("fmt"; "sync"; "time")
func produce(ch chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
}
}
func consume(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
time.Sleep(10 * time.Millisecond) // slower consumer
fmt.Println("consumed:", v)
}
}
func main() {
ch := make(chan int, 3)
var wg sync.WaitGroup
wg.Add(2)
go produce(ch, &wg)
go consume(ch, &wg) // BUG: produce closes before consume sees all
// In practice, coordinate close carefully
fmt.Println("decoupled")
}버퍼가 있는 채널을 이용한 세마포어
용량이 N인 버퍼가 있는 채널은 동시에 실행되는 goroutine 수를 N개로 제한할 수 있습니다. 간단한 세마포어로 사용할 수 있습니다:
package main
import ("fmt"; "sync"; "time")
func main() {
sem := make(chan struct{}, 3) // max 3 concurrent
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
i := i
go func() {
defer wg.Done()
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
fmt.Printf("job %d running\n", i)
time.Sleep(50 * time.Millisecond)
}()
}
wg.Wait()
}버퍼가 있는 채널 닫기
버퍼가 있는 채널을 닫으면 수신자가 닫힘을 감지하기 전에 버퍼에 남아 있는 값을 모두 꺼낼 수 있습니다:
package main
import "fmt"
func main() {
ch := make(chan int, 5)
for i := 1; i <= 3; i++ { ch <- i }
close(ch)
// Drain all buffered values after close:
for v := range ch {
fmt.Println(v) // 1, 2, 3
}
// range exits after channel closed and drained
}버퍼 유무 선택 기준
선택 기준입니다:
- 버퍼 없음: 확실한 동기적 인계, 엄격한 순서 보장, 동기적 만남을 통한 동기화
- 버퍼 있음: producer와 consumer의 속도 분리, 차단 감소, 세마포어 구현, N개의 결과 수집
- 기본값은 버퍼 없음으로 하고, 크기를 정당화할 수 있을 때만 버퍼를 추가합니다
- 버퍼 크기가 너무 작으면 → 여전히 차단되고, 너무 크면 → 버그가 숨겨집니다
버퍼가 있는 채널로 결과 수집
버퍼가 있는 채널을 사용하면 병렬로 실행되는 goroutine의 결과를 차단 없이 수집할 수 있습니다:
package main
import ("fmt"; "sync")
func square(n int) int { return n * n }
func main() {
nums := []int{1, 2, 3, 4, 5}
results := make(chan int, len(nums)) // buffer = goroutine count
var wg sync.WaitGroup
for _, n := range nums {
wg.Add(1)
n := n
go func() {
defer wg.Done()
results <- square(n)
}()
}
wg.Wait()
close(results)
for r := range results { fmt.Println(r) }
}가득 찬 버퍼로 인한 goroutine 누수
버퍼가 가득 차 있고 읽는 쪽이 없으면 송신자가 영원히 차단되어 goroutine 누수가 발생합니다:
package main
import ("fmt"; "time"; "runtime")
func main() {
ch := make(chan int, 2)
// Start goroutine that fills buffer and then blocks
go func() {
ch <- 1
ch <- 2
ch <- 3 // blocks: buffer full, no receiver
fmt.Println("never reached")
}()
time.Sleep(50 * time.Millisecond)
fmt.Println("goroutines:", runtime.NumGoroutine()) // 2 — leaked
}버퍼가 있는 채널로서의 time.After
time.After는 용량이 1인 버퍼가 있는 <-chan time.Time을 반환합니다. 런타임은 지정된 기간 후 해당 채널로 값을 보내고, 그 후 채널은 가비지 컬렉션됩니다:
package main
import ("fmt"; "time")
func doWithTimeout(work func() string) (string, bool) {
result := make(chan string, 1)
go func() { result <- work() }()
select {
case r := <-result:
return r, true
case <-time.After(100 * time.Millisecond):
return "", false
}
}
func main() {
r, ok := doWithTimeout(func() string {
time.Sleep(50 * time.Millisecond)
return "done"
})
fmt.Println(r, ok)
}빠른 확인
용량이 가득 찬 버퍼가 있는 채널로 송신하면 어떻게 되나요?
복습: 버퍼가 있는 채널
요약:
make(chan T, N)은 용량이 N인 버퍼가 있는 채널을 생성합니다- 버퍼가 가득 찼을 때만 송신이 차단되고, 버퍼가 비었을 때만 수신이 차단됩니다
len(ch)— 현재 항목 수,cap(ch)— 용량- producer와 consumer를 분리하고, 병렬 결과를 수집하며, 세마포어를 구현할 때 사용합니다
- 닫으면 EOF 전에 버퍼를 비울 수 있으며,
range ch가 이를 자동으로 처리합니다
자주 묻는 질문
“버퍼 채널” 강의는 무료인가요?
네 — “버퍼 채널” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Go Academy 강의 전체를 잠금 해제할 수 있습니다. Go Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“버퍼 채널”에서 뭘 배우나요?
용량, 채널 닫기 및 채널에 대한 range 브라우저에서 직접 실행하는 실습 코드로 Go Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Go Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Go Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“버퍼 채널” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Go Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Go Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.