💻 프로그래밍

채널

Channel

Go 언어에서 고루틴 간 안전한 통신을 위한 도구. 동시성 프로그래밍의 핵심 요소.

📖 상세 설명

채널(Channel)은 Go 언어에서 고루틴(goroutine) 간에 데이터를 안전하게 주고받기 위한 통신 메커니즘입니다. "공유 메모리로 통신하지 말고, 통신으로 메모리를 공유하라"는 Go의 철학을 구현한 핵심 기능으로, 동시성 프로그래밍에서 발생할 수 있는 경쟁 상태(race condition)를 방지합니다.

채널은 타입이 지정된 파이프라인으로 생각할 수 있습니다. make(chan int)로 정수를 전달하는 채널을 만들고, ch <- value로 값을 보내고, value := <-ch로 값을 받습니다. 기본적으로 채널은 동기적으로 동작하여, 보내는 쪽과 받는 쪽이 모두 준비될 때까지 블로킹됩니다.

버퍼 채널(buffered channel)은 make(chan int, 10)처럼 용량을 지정하여 생성합니다. 버퍼가 가득 차기 전까지는 보내는 쪽이 블로킹되지 않아 비동기적 통신이 가능합니다. 이는 생산자-소비자 패턴에서 성능 향상에 유용합니다.

select 문을 사용하면 여러 채널 연산을 동시에 기다릴 수 있습니다. 이를 통해 타임아웃 처리, 여러 소스에서 데이터 수신, 취소 신호 처리 등 복잡한 동시성 패턴을 우아하게 구현할 수 있습니다. close() 함수로 채널을 닫아 더 이상 값이 전송되지 않음을 알릴 수 있습니다.

💻 코드 예제

package main

import (
    "fmt"
    "time"
)

// 작업자 함수 - 채널로 결과 전송
func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(time.Millisecond * 100)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)

    // 3개의 워커 고루틴 시작
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // 5개의 작업 전송
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // 결과 수집
    for r := 1; r <= 5; r++ {
        result := <-results
        fmt.Printf("Result: %d\n", result)
    }
}

🗣️ 실무 대화 예시

시니어: 여러 API에서 데이터 가져오는 부분, 순차 처리하면 너무 느려요.

주니어: 고루틴으로 병렬 처리하면 될까요?

시니어: 네, 각 API 호출을 고루틴으로 띄우고 결과를 채널로 수집하면 됩니다. errgroup 패키지 쓰면 에러 처리도 깔끔해요.

면접관: Go에서 뮤텍스 대신 채널을 사용하는 이유는 무엇인가요?

지원자: 채널은 데이터 소유권을 이전하는 개념이라 경쟁 상태를 구조적으로 방지합니다. 뮤텍스는 공유 데이터를 보호하지만, 채널은 데이터를 복사하거나 이동시켜 한 번에 하나의 고루틴만 접근하도록 합니다.

면접관: 버퍼 채널과 언버퍼 채널의 사용 시나리오를 설명해주세요.

지원자: 언버퍼 채널은 동기화가 필요할 때 사용합니다. 보내는 쪽이 받는 쪽을 기다리므로 신호 전달에 적합해요. 버퍼 채널은 생산자-소비자 패턴에서 속도 차이를 흡수할 때 유용합니다.

리뷰어: 이 채널 닫는 위치가 좀 애매한데요. 패닉 발생할 수 있어요.

작성자: 어디가 문제인가요?

리뷰어: 고루틴 안에서 채널을 닫고 있는데, 보내는 쪽에서 닫아야 합니다. 받는 쪽에서 닫으면 보내는 쪽에서 닫힌 채널에 쓰려다 패닉이 발생해요.

⚠️ 주의사항

🔗 관련 용어

📚 더 배우기