티스토리 뷰
개요
원문: https://dave.cheney.net/2014/03/19/channel-axioms
참고링크 - channel 의 초기값 nil 은 무쓸모일까? https://goo.gl/K72ZNU
Axioms 란 공리라고 쓰는게 맞겠지만 자명한 이치라고 풀어써본다.
자명하다는 건 따로 설명이나 증명할 필요조차 없다는 것을 말한다.
따라서, 여기서는 Golang Channel 의 그야말로 자명한 이치를 알아보는 것이다.
→ 번역에 가까운, 원문을 나 자신이 이해하고 정리하며 쓰는 글이다.
개발자가 막연히 받아들이는 채널의 개념
1) 값들이 들어오고 나가는 Queue
2) Channel 이 꽉 차거나, 비어 있으면 블록된다.
- 꽉 차있는데 넣으려 하거나,
- 비어있는데 빼내려 하면 코드의 그 부분에서 멈추어서 기다리게 된다.
Channel 의 흔치 않은 네가지 특성
1) nil channel 에게 값을 넣으려하면 영원히 블록
2) nil channel 에게서 값을 받으려 하면 영원히 블록
3) closed channel 에 값을 넣으려하면 panic 발생
4) closed channel 의 값을 읽으려하면 zero 를 바로 리턴
1. nil channel 에게 값을 넣으려하면 영원히 블록
(참고) zero value 란? 문서링크: https://golang.org/ref/spec#The_zero_value Golang 에서 변수에 메모리가 할당 되었는데, 따로 초기화 = 초기값을 설정하지 않았을때 주어지는 값이다. 1) false: boolean type 2) 0: number type 3) nil: pointer, function, interface, slice, channel, map |
아래 코드는 c 라는 chan string 을 생성했는데, 별도의 초기화를 하지 않았으니
channel 인 c 의 값은 nil 이다.
이러한 nil 인 c 에 값을 넣으려 하니 dead lock 이 걸린다.
원문에서 제공하는 코드실습 링크: http://play.golang.org/p/1i4SjNSDWS
package main
func main() { var c chan string c <- "let's get started" // deadlock } |
|
2. nil channel 에게서 값을 받으려 하면 영원히 블록
마찬가지로 nil 인 c 에게서 값을 받으려해도 deadlock 에 걸려버린다.
원문에서 제공하는 코드실습 링크: https://play.golang.org/p/tjwSfLi7x0
package main
import "fmt"
func main() { var c chan string fmt.Println(<-c) // deadlock } |
|
위 두 경우처럼 nil channel 에 값을 넣거나 받으려 하는 경우에 대한 고찰
1) channel 변수를 선언할때에 버퍼 크기는 필수 사항이 아니며, 버퍼크기 미지정시 0이 된다. (= unbuffered)
2) 버퍼가 없는 상태란건 보관할 데가 없다는 거다.
- 보관할 데가 없는데 주면, 받아서 바로 줄 데가 생길때까지 대기
- 보관할 데가 없으니, 가진게 없는데 달라고 하면, 받는게 생길때까지 대기
→ block 상태이다.
3) 그런데 channel 이 nil 이란건 누군가
- 보내려 해도 보낼 주소를 모르고
- 받으려 해도 받으로 갈 데를 모르는 거다.
→ never unblock = deadlock
* 이런 무쓸모인 nil 값을 가진 channel 은 어디다 쓰나? 관련한 내용은 차차 확인해보자
- 링크: Why are there nil channels in Go? https://goo.gl/K72ZNU
3. closed channel 에 값을 넣으려하면 panic 발생
1) 100개의 버퍼를 가진 channel 을 만들고
2) 10개의 go routine 이 각각 10개씩 channel 에 값을 채운뒤 close() 하게 하였다.
3) 그러면 10개중 먼저 값을 다 채우고 close() 하는 go routine 이 있을 것이고,
4) 그 이후 close 된 channel 에 값을 넣으려는 go routine 때문에 panic 이 발생한다.
여기서는 현상만 확인하고, 해법은 링크로 대체한다. https://blog.golang.org/pipelines
원문에서 제공하는 코드실습 링크: https://play.golang.org/p/hxUVqy7Qj-
package main
import "fmt"
func main() { var c = make(chan int, 100) for i := 0; i < 10; i++ { go func() { for j := 0; j < 10; j++ { c <- j } close(c) }() } for i := range c { fmt.Println(i) } } |
|
4. closed channel 의 값을 읽으려하면 zero 를 바로 리턴
1) 버퍼가 3개인 channel 을 생성하고
2) 3개의 값을 넣은 다음, channel 을 close()
3) 이후에 이 channel 의 값을 읽으려 하면, 버퍼가 다 비워진 이후에는, 항상 0 값을 받는다.
아래는 개념을 좀더 명확히 이해하려 for loop 을 10번 돌려 보았다.
원문을 조금만 바꾼 코드실습 링크: https://play.golang.org/p/vHKTm-y5acl
package main
import "fmt"
func main() { c := make(chan int, 3) c <- 1 c <- 2 c <- 3 close(c) for i := 0; i < 10; i++ { fmt.Printf("%d ", <-c) // prints 1 2 3 0 0 ... } } |
|
Why are there nil channels in Go? TL;DR 버전
원문링크: https://goo.gl/K72ZNU
디테일한 내용은 원문을 참고하고, 핵심만을 정리해본다.
Channel 의 open, close 상태를 어떻게 알지?
1) close() 된 channel 의 값을 받으면 초기화 값이다. chan int 라면 0이 온다는 것이다.
2) 하지만, 이것만으로는 channel 의 close() 여부를 알 수 없다. channel 이 전달하는 값이 0일 수 있기 때문이다.
이때는 channel 에서 받는 두 번째 값으로 확인할 수 있다.
실습 링크: https://play.golang.org/p/paPwswZtWJu
package main
import "fmt"
func main() { c := make(chan int, 3) c <- 0 c <- 0 c <- 0 close(c) for i := 0; i < 6; i++ { v, ok := <-c fmt.Printf("%d, %v \n", v, ok) } } |
|
매번 channel 이 close() 인지 여부를 체크하기 싫다면?
만약 무한 루프에서 여러 channel 이 있고, 여기서 close 된 channel 을 skip 하고 싶다면?
매번 close 여부를 체크해서는 퍼포먼스 적으로 낭비이다.
원문 링크에서는 busy loop 라고 표현한다.
이때 nil 값을 활용하면 된다. 예제를 만들어보았다.
busy loop
1) a ,b channel 에 값들을 입력하고
2) go routine 에서 channel 들의 값을 받아 출력한다. 이때 close 여부를 확인한다.
3) 아래 코멘트 처리한 output 에서 "a is done" 이 계속 출력되는 부분이 바로 busy loop 이다.
* c channel 은 a, b channel 둘 다 close 되는 시점까지 main routine 이 종료되지 않도록 하는 용도로 사용
실습 링크: https://play.golang.org/p/phHAZwhZwhq
package main
import "fmt"
func main() { a := make(chan int, 3) b := make(chan int, 6) done := make(chan int)
a <- 1 a <- 2 a <- 3
b <- 1 b <- 2 b <- 3 b <- 4 b <- 5 b <- 6
close(a) close(b)
go func() { adone, bdone := false, false for adone == false || bdone == false { select { case v, ok := <-a: if !ok { fmt.Println("a is done") adone = true continue } fmt.Printf("a: %v\n", v) case v, ok := <-b: if !ok { fmt.Println("b is done") bdone = true continue } fmt.Printf("b: %v\n", v) } } close(done) }()
<-done }
/* output a: 1 b: 1 b: 2 b: 3 b: 4 a: 2 b: 5 b: 6 b is done a: 3 b is done b is done b is done a is done */ |
|
optimized with nil
아예 channel 이 nil 인지 여부를 체크하게 해놓고,
channel 이 close 된 상태, 더 이상 버퍼에 남은 데이터가 없으면 nil 로 만들어버리자.
이것이 busy loop 에 대한 원문의 해법이다.
실습 링크: https://play.golang.org/p/PQP_SXe3Zxl
package main
import "fmt"
func main() { a := make(chan int, 3) b := make(chan int, 6) done := make(chan int)
a <- 1 a <- 2 a <- 3
b <- 1 b <- 2 b <- 3 b <- 4 b <- 5 b <- 6
close(a) close(b)
go func() { for a != nil || b != nil { select { case v, ok := <-a: if !ok { fmt.Println("a is done") a = nil continue } fmt.Printf("a: %v\n", v) case v, ok := <-b: if !ok { fmt.Println("b is done") b = nil continue } fmt.Printf("b: %v\n", v) } } close(done) }()
<-done }
/* output b: 1 a: 1 b: 2 a: 2 a: 3 b: 3 a is done b: 4 b: 5 b: 6 b is done */ |
|
'golang' 카테고리의 다른 글
Slack slash command: Verifying requests from Slack: code (0) | 2019.02.25 |
---|---|
Slack slash command: Verifying requests from Slack (0) | 2019.02.19 |
slack slash command 는 무얼 보내주는 걸까 (0) | 2019.02.15 |
Slack slash command + Golang server (0) | 2019.02.12 |
time.Sleep, time.Duration (0) | 2019.02.11 |
- Total
- Today
- Yesterday
- solid
- Gin
- ChatGPT
- 독서
- API
- 영화
- 클린 애자일
- agile
- bun
- JIRA
- folklore
- 2023
- golang
- 잡학툰
- Bug
- OpenAI
- strange
- 체호프
- 제이펍
- 독서후기
- postgres
- github
- pool
- 노션
- intellij
- 인텔리제이
- Shortcut
- go
- websocket
- notion
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |