yoongrammer

[Go] 채널 방향, 채널 버퍼링, Select 본문

언어/Go 언어

[Go] 채널 방향, 채널 버퍼링, Select

yoongrammer 2020. 11. 6. 02:12
728x90

관련 글

2020/11/05 - [Go 언어] - 채널(Channel) 이란?

 

목차

    [Go] 채널 방향, 채널 버퍼링, Select


    go 에서 채널 방향(channel directions), 채널 버퍼링(channel buffering), Select에 대해서 알아보도록 하겠습니다.

    채널 방향 (channel directions)


    채널은 고루틴 간의 통신 매체이므로 서로 데이터를 주고받을 수 있습니다.

    기본적으로 채널은 양방향이지만 단방향 채널도 만들 수 있습니다.

    단방향 채널을 만들때 make() 함수를 사용합니다.

    // 수신용 채널
    c1 := make(<-chan Type)
    
    // 송신용 채널
    c2 := make(chan<- Type)

    함수 인자를 사용하여 양방향 채널을 단방향 채널로 변환할 수 있습니다.

    양방향 채널을 수신 전용 또는 송신 전용 채널로 변환할 수 있지만 그 반대의 경우는 불가능합니다.

    package main
    
    import "fmt"
    
    func sending(s chan<- string) {
    	s <- "Hello"
    }
    
    func main() {
      // 양방향 채널 생성
      myChannel := make(chan string)
      
      // sending 함수를 호출하여 양방향 채널을 송신용 채널로 변경
      go sending(myChannel)
    
      fmt.Println(<-myChannel)
    }

    Output:

    Hello

    단방향 채널을 사용하면 프로그램의 타입 안전성을 높일 수 있습니다.

    채널 버퍼링(channel buffering)


    go 언어에서 채널은 unbuffered channel, buffered channel 두 가지 종류가 있습니다.

    Unbuffered Channel

    기본적으로 채널은 Unbuffered입니다. Unbufferd 채널은 버퍼링 되지 않기 때문에 수신자가 데이터를 받을 때까지 송신자는 block이 걸리기 때문에 동기화가 보장됩니다.

    package main
    
    import "fmt"
    
    func main() {
      // Unbuffered 채널 생성
      done := make(chan bool)
    
      fmt.Println("Main start")
      go func() {
        done <- ture
        for i:= 0; i < 3; i++ {
          fmt.Println("Goroutine1: ", i)
        }
      } ()
    
      time.Sleep(100 * time.Millisecond)
      go func() {
        for i:= 0; i < 3; i++ {
          fmt.Println("Goroutine2: ", i)
        }
      <-done
      } () 
    
      time.Sleep(100 * time.Millisecond)
      fmt.Println("Main end")
    }

    Output:

    Main start
    Goroutine2:  0
    Goroutine2:  1
    Goroutine2:  2
    Goroutine1:  0
    Goroutine1:  1
    Goroutine1:  2
    Main end

    고루틴 1은 고루틴 2에서 데이터를 수신하기 전까지 block이 걸리기 때문에 위와 같은 순서로 출력을 하게 됩니다.

    728x90

     

    Buffered Channel

    Buffered 채널은 수신자가 받을 준비가 되어 있지 않아도 지정된 버퍼만큼 데이터를 보내고 계속 다른 일을 수행할 수 있기 때문에 비동기적으로 동작할 수 있습니다.

    buffered 채널에서 송신 측은 버퍼가 가득 찬 경우에만 차단되고 수신 측에서는 버퍼가 비어 있을 때만 차단됩니다.

     

    Bufferd channel은 make(chan Type, N) 함수를 사용하여 만들 수 있습니다.

    두 번째 매개변수 N에 사용할 버퍼 개수를 넣으면 됩니다.

    myChannel := make(chan Type, N)

    N이 0이면 Unbuffered Channel과 같습니다.

     

    위 예제에서 bufferd channel 방식으로 변경해보도록 하겠습니다.

    package main
    
    import "fmt"
    
    func main() {
      // Bufferd 채널 생성
      done := make(chan bool, 1)
    
      fmt.Println("Main start")
      go func() {
        done <- ture
        for i:= 0; i < 3; i++ {
          fmt.Println("Goroutine1: ", i)
        }
      } ()
    
      time.Sleep(100 * time.Millisecond)
      go func() {
        for i:= 0; i < 3; i++ {
          fmt.Println("Goroutine2: ", i)
        }
      <-done
      } () 
    
      time.Sleep(100 * time.Millisecond)
      fmt.Println("Main end")
    }

    Output:

    Main start
    Goroutine1:  0
    Goroutine1:  1
    Goroutine1:  2
    Goroutine2:  0
    Goroutine2:  1
    Goroutine2:  2
    Main end

    고루틴 1에서 메시지를 보내고 block 되지 않고 바로 다음 일을 수행하기 때문에 위와 같은 순서로 출력이 됩니다.

    Select


    Go 언어에서 select 문은 switch문과 비슷하지만 select 문에서 case문은 채널에서 전송 또는 수신 작업을 의미합니다.

     

    select는 case들 중 하나가 실행될 때까지 대기합니다. 만약 다수의 case가 준비되는 경우에는 select가 무작위로 하나를 선택합니다.

    select문에 default문이 있으면, case문 채널이 준비되지 않더라도 대기하지 않고 바로 default문을 실행합니다.

    select {
      case <-ch1:
        // 채널1에 값이 들어왔을 때 수행
      case <-ch2:
        // 채널2에 값이 들어왔을 때 수행
      default:
        // 모든 채널에 값이 들어오지 않았을 때 수행
    }

     

    보통 select를 계속 처리하기 위해 for 반복문을 사용합니다.

    package main
    
    import (
      "fmt"
      "time"
    )
    
    func send1(channel1 chan string) {
    
      time.Sleep(100 * time.Millisecond)
      channel1 <- "message1"
    }
    
    func send2(channel2 chan string) {
    
      time.Sleep(200 * time.Millisecond)
      channel2 <- "message2"
    }
    
    func main() {
    
      ch1 := make(chan string)
      ch2 := make(chan string)
    
      go send1(ch1)
      go send2(ch2)
    
      for {
        select {
          case msg1 := <-ch1: // ch1에 값이 들어오면 메세지 출력
            fmt.Println("recv: ", msg1)
    
          case msg2 := <-ch2: // ch2에 값이 들어오면 메세지 출력후 종료
            fmt.Println("recv: ", msg2)
            return
          }
      }
    }

    Output:

    recv:  message1
    recv:  message2

     

     

    728x90

    '언어 > Go 언어' 카테고리의 다른 글

    gdlv - Go언어 디버거  (0) 2020.11.22
    Delve - Go언어 디버거  (0) 2020.11.13
    [Go] 채널(Channel) 이란?  (0) 2020.11.05
    고루틴(goroutine) 이란?  (0) 2020.11.04
    Go 언어 설치  (0) 2020.11.03
    Comments