채널 사용하기


이재홍 http://www.pyrasis.com 2014.12.17 ~ 2015.02.07



셀렉트 사용하기

Go 언어는 여러 채널을 손쉽게 사용할 수 있도록 select 분기문을 제공합니다.

  • select { case <- 채널: 코드 }
select {
case <-채널1:
	// 채널1에 값이 들어왔을 때 실행할 코드를 작성합니다.
case <-채널2:
	// 채널2에 값이 들어왔을 때 실행할 코드를 작성합니다.
default:
	// 모든 case의 채널에 값이 들어오지 않았을 때 실행할 코드를 작성합니다.
}

select 분기문은 switch 분기문과 비슷하지만 select 키워드 뒤에 검사할 변수를 따로 지정하지 않으며 각 채널에 값이 들어오면 해당 case가 실행됩니다(close 함수로 채널을 닫았을 때도 case가 실행됩니다). 그리고 보통 select를 계속 처리할 수 있도록 for로 반복해줍니다(반복하지 않으면 한 번만 실행하고 끝냅니다).

switch 분기문과 마찬가지로 select 분기문도 default 케이스를 지정할 수 있으며 case에 지정된 채널에 값이 들어오지 않았을 때 즉시 실행됩니다. 단, default에 적절한 처리를 하지 않으면 CPU 코어를 모두 점유하므로 주의합니다.

다음은 채널 2개를 생성하고 100밀리초, 500밀리초 간격으로 숫자와 문자열을 보낸 뒤 꺼내서 출력합니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan int)    // int형 채널 생성
	c2 := make(chan string) // string 채널 생성

	go func() {
		for {
			c1 <- 10                           // 채널 c1에 10을 보낸 뒤
			time.Sleep(100 * time.Millisecond) // 100 밀리초 대기
		}
	}()

	go func() {
		for {
			c2 <- "Hello, world!"              // 채널 c2에 Hello, world!를 보낸 뒤
			time.Sleep(500 * time.Millisecond) // 500 밀리초 대기
		}
	}()

	go func() {
		for {
			select {
			case i := <-c1:                // 채널 c1에 값이 들어왔다면 값을 꺼내서 i에 대입
				fmt.Println("c1 :", i) // i 값을 출력
			case s := <-c2:                // 채널 c2에 값이 들어왔다면 값을 꺼내서 s에 대입
				fmt.Println("c2 :", s) // s 값을 출력
			}
		}
	}()

	time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}

실행 결과

c2 : Hello, world!
c1 : 10
c1 : 10
c1 : 10
c1 : 10
c1 : 10
c2 : Hello, world!
c1 : 10
... (생략)

실행을 해보면 select에서 번갈아가면서 10과 Hello, world!가 출력됩니다. 채널 c2에 Hello, world!를 보낸 쪽에서는 500 밀리초 대기하고, 채널 c1에 10을 보낸 쪽에서는 100 밀리초 대기하므로 10이 더 많이 출력됩니다.

case에서는 case i := <-c1:처럼 채널에서 값을 꺼낸 뒤 변수에 바로 저장할 수 있습니다. 만약 꺼낸 값을 사용하지 않는다면 case <-c1:처럼 변수를 생략해도 됩니다.

select {
case i := <-c1:                // 채널 c1에 값이 들어왔다면 값을 꺼내서 i에 대입
	fmt.Println("c1 :", i) // i 값을 출력
case s := <-c2:                // 채널 c2에 값이 들어왔다면 값을 꺼내서 s에 대입
	fmt.Println("c2 :", s) // s 값을 출력
}

time.After 함수를 사용하면 시간 제한 처리를 할 수 있습니다. time.After는 특정 시간이 지나면 현재 시간을 채널로 보냅니다.

select {
case i := <-c1:
	fmt.Println("c1 : ", i)
case s := <-c2:
	fmt.Println("c2 : ", s)
case <-time.After(50 * time.Millisecond): // 50 밀리초 후 현재 시간이 담긴 채널이 리턴됨
	fmt.Println("timeout")
}

이처럼 case에서는 time.After와 같이 받기 전용 채널을 리턴하는 함수를 사용할 수 있습니다.

select 분기문은 채널에 값을 보낼 수도 있습니다.

  • case 채널 <- 값: 코드
package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan int)    // int형 채널 생성
	c2 := make(chan string) // string 채널 생성

	go func() {
		for {
			i := <-c1                          // 채널 c1에서 값을 꺼낸 뒤 i에 대입
			fmt.Println("c1 :", i)             // i 값을 출력
			time.Sleep(100 * time.Millisecond) // 100 밀리초 대기
		}
	}()

	go func() {
		for {
			c2 <- "Hello, world!"              // 채널 c2에 Hello, world!를 보냄
			time.Sleep(500 * time.Millisecond) // 500 밀리초 대기
		}
	}()

	go func() {
		for { // 무한 루프
			select {
			case c1 <- 10:                 // 매번 채널 c1에 10을 보냄
			case s := <-c2:                // c2에 값이 들어왔을 때는 값을 꺼낸 뒤 s에 대입
				fmt.Println("c2 :", s) // s 값을 출력
			}
		}
	}()

	time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행
}

실행 결과

c2 : Hello, world!
c1 : 10
c1 : 10
c1 : 10
c1 : 10
c1 : 10
c2 : Hello, world!
c1 : 10
... (생략)

select 분기문에서 채널에 값을 보내는 case가 있다면 항상 값을 보냅니다. 하지만 채널에 값이 들어왔을 때는 값을 받는 case가 실행됩니다.

여기서는 select에서 매번 채널 c1에 값을 보내지만 채널 c2에 값이 들어오면 c2에서 값을 꺼내서 출력합니다.

for { // 무한 루프
	select {
	case c1 <- 10:             // 매번 채널 c1에 10을 보냄
	case s := <-c2:            // c2에 값이 들어왔을 때는 값을 꺼낸 뒤 s에 대입
		fmt.Println("c2 :", s) // s 값을 출력
	}
}

예제에서는 보내는 채널과 받는 채널을 두 개 사용했지만 다음과 같이 채널 c1 한 개로 select에서 값을 보내거나 받을 수도 있습니다.

c1 := make(chan int) // int형 채널 생성

go func() {
	for {
		i := <-c1                          // 채널 c1에서 값을 꺼낸 뒤 i에 대입
		fmt.Println("c1 :", i)             // i 값을 출력
		time.Sleep(100 * time.Millisecond) // 100 밀리초 대기
	}
}()

go func() {
	for {
		c1 <- 20                           // 채널 c1에 20을 보냄
		time.Sleep(500 * time.Millisecond) // 100 밀리초 대기
	}
}()

go func() {
	for { // 무한 루프
		select {                       // 채널 c1 한 개로 값을 보내거나 받음
		case c1 <- 10:                 // 매번 채널 c1에 10을 보냄
		case i := <-c1:                // c1에 값이 들어왔을 때는 값을 꺼낸 뒤 i에 대입
			fmt.Println("c1 :", i) // i 값을 출력
		}
	}
}()

time.Sleep(10 * time.Second) // 10초 동안 프로그램 실행

실행 결과

c1 : 20
c1 : 10
c1 : 10
c1 : 10
c1 : 10
c1 : 20
c1 : 10
... (생략)

여기서는 매번 채널에 값을 보내지만, select 분기문이 아닌 다른 쪽에서 채널에 값을 보내서 값이 들어왔다면 값을 받는 case가 실행됩니다.


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

01 June 2015