Menu
Coddy logo textTech

Channels & 통신

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 60번째.

채널(Channels)은 고루틴(goroutine) 간의 통신을 위한 Go의 주요 메커니즘입니다. 메모리를 직접 공유하기보다, 고루틴은 채널을 통해 값을 주고받으며 "메모리를 공유하여 통신하지 말고, 통신하여 메모리를 공유하라"는 Go의 철학을 따릅니다.

make 함수와 chan 키워드를 사용하여 채널을 생성합니다:

messages := make(chan string)

화살표 연산자 <-를 사용하여 값을 보내고 받습니다. 화살표는 데이터 흐름의 방향을 가리킵니다:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // 채널로 보내기
    }()
    
    msg := <-messages  // 채널로부터 받기
    fmt.Println(msg)   // 출력: hello
}

기본적으로, 송신과 수신은 상대방이 준비될 때까지 차단(block)됩니다. 고루틴(goroutine)이 "hello"를 보낼 때, main이 수신할 준비가 될 때까지 기다립니다. 이러한 차단 동작은 내장된 동기화를 제공하므로 time.Sleep이 필요하지 않습니다.

더 이상 값을 보내지 않겠다는 신호를 보내기 위해 채널을 닫을 수 있습니다. 수신자는 이를 감지할 수 있습니다:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

채널은 타입이 지정되어 있습니다. chan string은 문자열만 전달할 수 있습니다. 이러한 타입 안전성은 고루틴이 올바른 데이터 타입으로 통신하도록 보장하며, 런타임이 아닌 컴파일 타임에 오류를 잡아냅니다.

challenge icon

챌린지

쉬움

고루틴이 채널을 통해 어떻게 통신하는지 보여주는 메시지 중계 시스템을 구축해 보겠습니다. 각 단계가 동시에 실행되는, 메시지가 서로 다른 처리 단계 사이를 흐르는 파이프라인을 만들 것입니다.

코드는 두 개의 파일로 구성합니다:

  • relay.go: 메시지 처리 파이프라인을 정의합니다.

    ID (int)와 Content (string) 필드를 가진 Message 구조체를 생성하세요.

    파이프라인의 각 단계를 나타내는 세 가지 함수를 구현하세요:

    • Producer(messages []Message, out chan Message) - 메시지 슬라이스를 받아 각 메시지를 출력 채널로 보냅니다. 모든 메시지를 보낸 후, 완료를 알리기 위해 채널을 닫습니다.
    • Transformer(in chan Message, out chan Message) - 입력 채널에서 메시지를 받아 각 메시지의 내용을 대문자로 변환하고, 변환된 메시지를 출력 채널로 보냅니다. 입력 채널이 닫히면(두 개의 값을 반환하는 수신 형태를 사용하여 감지), 출력 채널을 닫습니다.
    • Consumer(in chan Message) []string - 입력 채널에서 모든 메시지를 받아 포맷된 문자열 슬라이스로 수집합니다. 각 문자열은 "Message %d: %s" 형식을 따라야 합니다. 채널이 닫히면 슬라이스를 반환합니다.
  • main.go: 파이프라인을 설정하고 동시 메시지 흐름을 조율합니다.

    메시지 수를 읽은 다음, 각 메시지의 ID와 내용을 읽습니다. 메시지를 생성하고 세 개의 파이프라인 단계를 연결할 두 개의 채널을 설정하세요. Producer와 Transformer를 고루틴으로 실행하고, 메인 고루틴에서 Consumer를 실행하여 결과를 수집합니다. 각 결과를 별도의 줄에 출력하세요.

다음과 같은 입력이 제공됩니다:

  • 1행: 메시지 수 (정수)
  • 다음 행들: 각 메시지에 대해 두 줄 - 메시지 ID (정수), 그 다음 내용 (문자열)

예를 들어, 다음과 같이 주어지면:

2
1
hello world
2
go channels

출력은 다음과 같아야 합니다:

Message 1: HELLO WORLD
Message 2: GO CHANNELS

파이프라인은 다음과 같이 흐릅니다: Producer가 메시지를 보냄 → Transformer가 대문자로 변환 → Consumer가 결과를 수집하고 포맷팅함. 각 화살표는 동시 단계를 연결하는 채널을 나타냅니다. 대문자 변환을 위해 strings 패키지를 사용하세요.

치트 시트

채널은 고루틴 간의 통신을 위한 Go의 주요 메커니즘입니다. chan 키워드와 함께 make를 사용하여 채널을 생성합니다:

messages := make(chan string)

화살표 연산자 <-를 사용하여 값을 보내고 받습니다. 화살표는 데이터 흐름의 방향을 가리킵니다:

messages <- "hello"  // 채널로 보내기
msg := <-messages    // 채널로부터 받기

기본적으로, 송신과 수신은 상대방이 준비될 때까지 차단(block)되어 내장된 동기화 기능을 제공합니다:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // 채널로 보내기
    }()
    
    msg := <-messages  // 채널로부터 받기
    fmt.Println(msg)   // 출력: hello
}

더 이상 값을 보내지 않음을 알리려면 채널을 닫습니다. 수신측은 두 개의 값을 반환하는 수신 형태를 사용하여 채널이 닫혔는지 감지할 수 있습니다:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

채널은 타입이 지정됩니다. chan string은 문자열만 전달할 수 있어 컴파일 타임 타입 안정성을 제공합니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// 메시지 수 읽기
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// 메시지 읽기
	messages := make([]Message, n)
	for i := 0; i < n; i++ {
		idLine, _ := reader.ReadString('\n')
		id, _ := strconv.Atoi(strings.TrimSpace(idLine))
		contentLine, _ := reader.ReadString('\n')
		content := strings.TrimSpace(contentLine)
		messages[i] = Message{ID: id, Content: content}
	}

	// TODO: 파이프라인 단계를 연결할 두 개의 채널 생성

	// TODO: Producer를 고루틴으로 실행

	// TODO: Transformer를 고루틴으로 실행

	// TODO: 메인 고루틴에서 Consumer를 실행하고 결과 수집

	// TODO: 각 결과를 별도의 줄에 출력
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨