채널-3

채널-3

채널에 대해 알아보자.


채널 select문


채널에서의 select문은 ‘분기문’인 switch문과 거의 흡사한 용법으로 사용됩니다. 채널 데이터를 수신하거나 송신할 때 select문을 사용하면 먼저 송신이 된 채널을 수신하고 해당 부분을 실행하거나 채널로 데이터를 송신할 수 있습니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan bool)
	ch2 := make(chan bool)

	go func() {
		for {
			time.Sleep(1000 * time.Millisecond)
			ch1 <- true
		}
	}()

	go func() {
		for {
			time.Sleep(500 * time.Millisecond)
			ch2 <- true	
		}
	}()

	go func() {
		for {
			<-ch1
			fmt.Println("ch1 수신")
			<-ch2
			fmt.Println("ch2 수신")
		}
	}()

	time.Sleep(5 * time.Second)
}
ch1, ch2 채널을 생성하는 main 루틴
1000밀리초를 대기하고 ch1에 'true'를 송신하는 것을 반복하는 고루틴
500밀리초를 대기하고 ch2에 'true'를 송신하는 것을 반복하는 고루틴
ch1 수신, "ch1 수신" 출력, ch2 수신, "ch2 수신" 출력을 순서대로 반복하는 고루틴

고루틴이 모두 동시(Concurrency)에 시작되어 ch2 채널이 ch1 채널보다 데이터가 먼저 송신돼도 수신 루틴에서 ch1 채널을 먼저 수신하기 때문에 ch1 채널을 수신할 때까지 기다려야합니다. ch1 채널 데이터가 1000밀리초만에 수신되면 이미 송신을 마친 ch1 채널 데이터가 수신됩니다. 이 과정을 반복하면 결국 ch1 채널과 ch2 채널은 동일하게 1000밀리초를 기다리는 것입니다.

분명히 ch2 채널이 이미 송신 되었음에도 불구하고 ch1 채널이 송신되는 것을 기다리는 것은 굉장히 효율적이지 않습니다. 따라서 이러한 상황에 송신된 채널을 선택적으로 처리하는 select문을 사용해 채널 데이터 송/수신 효율을 높일 수 있습니다.

select {
case <- 채널1이름:
	//실행할 구문
case <- 채널2이름;
	//실행할 구문
	...
default:
	//모든 case의 채널에 데이터가 송신되지 않았을 때 실행
}
package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan bool)
	ch2 := make(chan bool)

	go func() {
		for {
			time.Sleep(1000 * time.Millisecond)
			ch1 <- true
		}
	}()

	go func() {
		for {
			time.Sleep(500 * time.Millisecond)
			ch2 <- true	
		}
	}()

	go func() {
		for {
			select {
			case <-ch1:
				fmt.Println("ch1 수신")
			case <-ch2:
				fmt.Println("ch2 수신")
			}
			
		}
	}()

	time.Sleep(5 * time.Second)
}

수신 루틴에서 select문을 사용했기 때문에 ch2 채널은 ch1 채널에 데이터가 송신될 때까지 기다리지 않고 송신되면 바로 select문에 의해 바로 수신됩니다. 그리고 switch문에서 case 뒤에 표현식을 사용할 수 있었던 것처럼 select문에서는 "case 변수 := <- 채널:" 형식으로 데이터 수신과 동시에 변수에 데이터를 초기화할 수 있습니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go func() {
		for {
			time.Sleep(1000 * time.Millisecond)
			ch1 <- 10
		}
	}()

	go func() {
		for {
			time.Sleep(500 * time.Millisecond)
			ch2 <- 20	
		}
	}()

	go func() {
		for {
			select {
				case a := <-ch1:
					fmt.Printf("ch1 데이터 %d 수신\n", a)
				case b := <-ch2:
					fmt.Printf("ch2 데이터 %d 수신\n", b)
			}
			
		}
	}()

	time.Sleep(5 * time.Second)
}

맨 위에서 언급한 것처럼 select문송신된 채널이 있을 때, 그 채널을 수신하는 기능을 하는 것 뿐만이 아니라 case를 이용해 채널에 데이터를 송신할 수 있습니다. select문에 채널에 데이터를 송신하는 case가 있다면 항상 데이터를 송신하고, 채널에 데이터가 수신됐다면 데이터를 받는 case가 실행됩니다.
쉽게 말해서 송신자와 수신자가 모두 select문에 있을때 송신된 데이터가 없으면 계속 송신자 case를 실행해 데이터를 송신합니다.

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	ch3 := make(chan string)
	
	go func() {
		for {
			time.Sleep(200 * time.Millisecond)
			c := <- ch3
			fmt.Printf("ch3 데이터 %s 수신\n", c)
		}
	}()

	go func() {
		for {
			time.Sleep(1000 * time.Millisecond)
			ch1 <- 10
		}
	}()

	go func() {
		for {
			time.Sleep(500 * time.Millisecond)
			ch2 <- 20
		}
	}()

	go func() {
		for {
			select {
				case a := <-ch1:
				fmt.Printf("ch1 데이터 %d 수신\n", a)
				case b := <-ch2:				
				fmt.Printf("ch2 데이터 %d 수신\n", b)
				case ch3 <- "goorm":
				}
		}
	}()
	
	time.Sleep(5 * time.Second)
}

© 2022. All rights reserved. 신동민의 블로그