golang

Golang Channel 의 자명한 이치 (Axioms)

주먹불끈 2019. 2. 18. 10:51

개요

 

원문: 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

        <- "let's get started" // deadlock

}

Colored by Color Scripter

 

 

 

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

}

Colored by Color Scripter

 

 

 

경우처럼 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 int100)

        for i := 0; i < 10; i++ {

                go func() {

                        for j := 0; j < 10; j++ {

                                <- j

                        }

                        close(c)

                }()

        }

        for i := range c {

                fmt.Println(i)

        }

}

Colored by Color Scripter

 

 

 

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 int3)

            <- 1

            <- 2

            <- 3

            close(c)

            for i := 0; i < 10; i++ {

                        fmt.Printf("%d "<-c) // prints 1 2 3 0 0 ...

            }

}

Colored by Color Scripter

 

 

 

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() {

    := make(chan int3)

    <- 0

    <- 0

    <- 0

    close(c)

    for i := 0; i < 6; i++ {

        v, ok := <-c

        fmt.Printf("%d, %v \n", v, ok) 

    }

}

Colored by Color Scripter

 

 

 

매번 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() {

    := make(chan int3

    := make(chan int6

    done := make(chan int)

    

    <- 1

    <- 2

    <- 3

    

    

    <- 1

    <- 2

    <- 3

    <- 4

    <- 5

    <- 6

    

    close(a)

    close(b)

 

    go func() {

        adone, bdone := falsefalse

        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

*/

Colored by Color Scripter

 

 

 

optimized with nil

 

아예 channel nil 인지 여부를 체크하게 해놓고,

channel close 상태, 이상 버퍼에 남은 데이터가 없으면 nil 만들어버리자.

이것이 busy loop 대한 원문의 해법이다.

 

 

실습 링크: https://play.golang.org/p/PQP_SXe3Zxl

 

package main

 

import "fmt"

 

func main() {

    := make(chan int3

    := make(chan int6

    done := make(chan int)

    

    <- 1

    <- 2

    <- 3

    

    

    <- 1

    <- 2

    <- 3

    <- 4

    <- 5

    <- 6

    

    close(a)

    close(b)

 

    go func() {

        for != nil || b != nil {

            select {

            case v, ok := <-a:

                if !ok {

                    fmt.Println("a is done")

                    = nil

                    continue

                }

                fmt.Printf("a: %v\n", v)

            case v, ok := <-b:

                if !ok {

                    fmt.Println("b is done")

                    = 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

*/

 


반응형