일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 양자역학
- Go
- nil
- 키보드
- mistel md600 alpha
- 프라파손
- 스플릿키보드
- Golang
- 엘리스배열
- mf642cdw
- 음각몰드
- 건프라
- md600alpha
- 패널라인
- 체리적축저소음
- Go 언어
- 벤치마크
- 바로코
- magicsculpt
- emacs
- Griffiths
- 고양이책
- Reflection
- 다이어트
- 터미널
- go언어
- mopria
- driverless
- 이맥스
- 리플렉션
- Today
- Total
Meandering Trajectory
select 문: 여러 채널에서 데이터 읽어 처리하기 본문
고루틴과 채널은 Go의 대표적인 기능이다. 고루틴은 서로 다른 작업이 함께 진행될 수 있도록 해주고 채널은 고루틴들이 통신을 통해 협력할 수 있게 한다.
select 문을 이용하면 여러 채널을 모니터링하다가 먼저 데이터가 도착한 채널의 데이터를 읽도록 할 수 있다. 이런 기능이 없다면 프로그래머는 각 채널별로 별도의 고루틴을 할당해야 한다. Go 스케쥴러의 특성상 지나치게 많은 고루틴을 사용하는 것은 부작용이 있을 수 있으므로 한개의 고루틴으로 여러 가지 일을 할 수 있다는 것은 여러 모로 좋은 일이다.
이렇게 한개의 스레드를 이용해 여러 데이터 소스(이 경우 채널)를 한꺼번에 들여다 보고 있다가 먼저 도착한 데이터를 우선 읽어 처리하는 방식의 프로그래밍을 이벤트 기반 프로그래밍(event-driven programming)이라고 한다.
select의 사용법
아래 코드는 a, b 두개의 채널에 대해 select 문을 사용한 예로 이렇게 하면 둘 중 어느 것이든 먼저 데이터가 도착한 채널의 데이터를 읽을 수 있다.
package main import "fmt" func main() { a := make(chan int32) b := make(chan string) select { case x := <-a: fmt.Printf("a 채널에서 읽은 데이터: %v\n", x) case y := <-b: fmt.Printf("b 채널에서 읽은 데이터: %v\n", y) } }
하지만 이 코드를 컴파일하고 실행하면 다음과 같이 에러가 발생한다.
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select]:
main.main()
/Users/qcollapse/work/md/blog/2017/11/main.go:17 +0x26f
그 이유는 채널에 쓰기를 하는 고루틴이 없기 때문이다. 그러니까 a, b 어느 채널에도 데이터가 배달되지 않으니 프로그래밍 영원한 대기 상태(deadlock)에 빠지는 것을 막기 위해 스스로 종료된 것이다.
이 에러를 통해 select 문의 중요한 동작을 알 수 있다:
제대로 실행되게 코드 수정하기
다음과 같이 코드를 수정하면 프로그램이 정상적으로 실행된다.
package main import "fmt" func main() { a := make(chan int32) b := make(chan string) go func() { a <- int32(7) }() go func() { b <- "일곱개" }() select { case x := <-a: fmt.Printf("a 채널에서 읽은 데이터: %v\n", x) case y := <-b: fmt.Printf("b 채널에서 읽은 데이터: %v\n", y) } }
그런데 이 코드를 컴파일한 뒤 실행하면
b 채널에서 읽은 데이터: 일곱개
나
a 채널에서 읽은 데이터: 7
중 하나만 출력된다.
여기서 주목해야 할 점은 다음과 같다.
- 둘 중 한쪽 채널의 데이터만 출력된다.
- a 채널의 데이터가 출력될 때가 있고 b 채널의 데이터가 출력될 떄가 있다.
1번 현상의 원이은 select 문이 모니터링 중이던 채널 중 먼저 데이터가 도착하는 쪽의 case 절만 수행한 뒤 실행을 끝내기 때문이다. 즉 select 문은 최초의 데이터가 독찰할 때 딱 한번만 실행된다. (select 문은 loop가 아니다.)
2번과 같은 현상이 생기는 원인은 조금 더 복잡하다. 위 프로그램이 실행되면 main 함수와 두 개의 고루틴은 기본적으로 모두 다른 스레드에서 실행된다. 이때 채널에 데이터를 기록하는 두 개의 고루틴 중 어느 고루틴이 채널에 쓰기를 먼저 수행할 것인지는 운영체제의 상황에 따라 그때그떄 다르다. 그래서 a 채널에 데이터가 먼저 출력될 때가 있고 b 채널의 데이터가 먼저 출력될 때가 있는 것이다.
두 채널의 데이터가 모두 출력되게 하기
다음과 같이 select 문이 두번 실행되도록 하면 각 채널의 데이터가 모두 출력된다.
package main import "fmt" func main() { a := make(chan int32) b := make(chan string) go func() { a <- int32(7) }() go func() { b <- "일곱개" }() for i := 0; i < 2; i++ { select { case x := <-a: fmt.Printf("a 채널에서 읽은 데이터: %v\n", x) case y := <-b: fmt.Printf("b 채널에서 읽은 데이터: %v\n", y) } } }
loop를 이용해 select 문이 두 번 실행되게 했다.
- 고루틴(혹은 스레드) 간이든 네트워크를 통한 것이든 통신과 관련하여 이렇게 대기하는 상황을 흔히 블로킹(blocking) 된다고 이야기 한다. select 문이 실행되는 시점에 읽을 데이터가 없을 때 대기하지 않고 select 문을 빠져나오게 하고 싶다면 select에 default case를 추가하면 된다. 물론 이경우 데이터가 읽힐 때까지 select 문을 적절히 반복해서 수행하는 것이 필요하다. [본문으로]
'컴퓨터 > GoLang' 카테고리의 다른 글
Go 코드에서의 에러 처리 (0) | 2017.11.18 |
---|---|
new와 make (0) | 2017.11.12 |
Go 언어: 빈 슬라이스와 nil (0) | 2017.10.20 |
클로저(Closure)와 데코레이터(Decorator) 패턴 (0) | 2017.08.14 |
아니 Go의 상태가… (0) | 2017.08.12 |