Go 채널은 그 채널을 통해 데이터를 주고 받는 통로라 볼 수 있다. 채널은 이전 포스팅에서 얘기했듯이 maek() 함수를 통해 미리 생성되어야 하며, 채널 연산자 <- 를 통해 데이터를 주고 받는다.
채널은 흔히 goroutine들 사이 데이터를 주고 받는데 사용되는데, 상대편이 준비될 때 까지 채널에서 대기함으로써 별도의 lock을 걸지 않고 데이터를 동기화 하는데 사용된다.
채널을 생성할 때는 make() 함수에 어떤 타입의 데이터를 채널에서 주고 받을지를 미리 지정해 주어야 한다. 채널로 데이터를 보낼 때는 '채널명 <- 데이터' 와 같이 사용하고, 채널로부터 데이터를 받을 때는 '<- 채널명' 으로 이용한다.
ch := make(chan int)
go func() {
ch <- 123
}()
var i int
i = <- ch
fmt.Println(i)
////
123
위 예제는 마지막에서 채널로부터 데이터를 받고 있는데, 상대편 goroutine에서 데이터를 전송할때까지 계속 대기하게 된다. 따라서, 이 예제에서는 time.Sleep() 나 fmt.Scanf() 같이 goroutine이 끝날 때 까지 기다리는 코드를 적지 않아도 go 함수가 실행되는 것을 볼 수 있다.
done := make(chan bool)
go func() {
for i :=0; i < 10; i++ {
fmt.Println(i)
}
done <- true
}()
test := <- done
go 채널은 수신자와 송신자가 서로를 기다리는 속성때문에, 이를 이용해 위 예제 처럼 Go루틴이 끝날 때 까지 기다리는 기능을 구현할 수 있다. 익명함수를 사용한 한 Go 루틴에서 어떤 작업이 실행되고 있을 때, 메인 루틴은 <- done 에서 계속 수신하며 대기하고 있게 된다.
Go 채널은 2 가지의 채널이 있는데, Unbuffered Channel, Buffered Channel 이 있다. 위의 예제는 Unbuffered Channel 로서 이 채널에서는 하나의 수신자가 데이터를 받을 때 까지 송신자가 데이터를 보내는 채널에 묶여 있게 된다.
하지만 Buffered Cahnnel을 사용하면 비록 수신자가 받을 준비가 되어 있지 않을 지라도 지정된 버퍼만큼 데이터를 보내고 계속 다른 일을 수행할 수 있다.
버퍼 채널은 make(chan type, N) 함수를 통해 생성되는데, 두 번째 파라미터 N에 사용할 버퍼 갯수를 넣는다. make(chan int, 10)은 10개의 정수형을 갖는 버퍼 채널을 만든다.
버퍼 채널을 이용하지 않는 경우, 에러를 발생 시킨다. 왜냐하면 메인 루틴에서 채널에 값을 보내면서 수신자를 기다리고 있는데, 이 채널을 받는 수신자 Go 루틴이 없기 때문이다.
unbuffered channel에서는 보내는 작업과 받는 작업이 동시에 이루어져야 한다. 허나 채널을 생성하고 바로 값을 보내는 방식은 안된다.
package channeltest
import "fmt"
func Channeltest2() {
c := make(chan int)
// 수신 루틴이 없으므로 데드락 에러가 발생한다.
c <- 1
// 코멘트해도 데드락이 걸린다 (별도의 go 루틴이 없기 때문)
fmt.Println(<-c)
}
허나 아래와 같이 buffered channel을 이용하면, 수신자가 당장 없더라도 최대버퍼 수 까지 데이터를 보낼 수 있으므로, 에러가 발생하지 않는다.
package channeltest
import "fmt"
func Channeltest2() {
// c := make(chan int)
// // 수신 루틴이 없으므로 데드락 에러가 발생한다.
// c <- 1
// // 코멘트해도 데드락이 걸린다 (별도의 go 루틴이 없기 때문)
// fmt.Println(<-c)
// 아래와 같이 해도 데드락이 걸림, 버퍼크기는 9인데 값을 10번 보내기 때문
c2 := make(chan int, 9)
for i := 0 ; i < 10; i++ {
c2 <- i
}
fmt.Println(<-c2)
}
채널을 함수의 파라미터도 전달할 때, 일반적으로 송수신을 모두 하는 채널을 전달하지만, 특별히 해당 채널로 송신만 할 것인지 혹은 수신만할 것인지를 지정할 수도 있다.
송신 파라미터는 (p chan <- int)와 같이 chan <- 을 사용하고, 수신 파라미터는 (p <-chan int)와 같이 <- chan 을 사용한다. 만약 송신 채널 파라미터에서 수신을 한다거나, 수신 채널에 송신을 하게 되면, 에러가 발생한다.
package channeltest
import "fmt"
func Channeltest3() {
ch := make(chan string, 1)
sendChan(ch)
receiveChan(ch)
}
func sendChan(ch chan<- string) {
ch <- "Data"
// x:= <- ch 에러발생
}
func receiveChan(ch <-chan string) {
data := <-ch
fmt.Println(data)
}
채널을 오픈한 후 데이터를 송신한 후, close() 함수를 사용해 채널을 닫을 수 있다. 채널을 닫게 되면, 해당 채널로는 더 이상 송신을 할 수 없지만, 채널이 닫힌 이후에도 계속 수신은 가능하다.
채널 수신에 사용되는 <- ch 는 두 개의 리턴값을 갖는데, 하나는 채널 메시지이고, 두 번째는 수신이 제대로 되었는가이다. 만약 채널이 닫혀있다면 두 번째 리턴값은 false를 나타낸다.
package channeltest
import "fmt"
func Channeltest4() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
close(ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
// ch <- 3 'send on closed channel' 에러 발생
a,b := <- ch
println(a,b, "Qwe")
}
////
0 false Qwe
채널에서 송신자가 송신을 한 후, 채널을 닫을 수 있다. 그리고 수신자는 임의의 갯수의 데이터를 채널이 닫힐 때까지 계속 수신할 수 있다.
package channeltest
import "fmt"
func Channeltest5() {
ch := make(chan int, 2)
// 채널에 송신
ch <- 1
ch <- 2
// 채널을 닫는다
close(ch)
// 방법 1
// 채널이 닫힌 것을 감지할 때 까지 계속 수신
for {
if i, success := <-ch; success {
fmt.Println(i)
} else {
break
}
}
// 방법2
// 채널 range 문
for i := range ch {
fmt.Println(i)
}
}
위 코드처럼 첫 번째 방법은 ch의 두 번째 인자를 이용하는 것이고, 두 번째 방법은 for range문으로 간결하게 표현하는 것이다. 채널 range문은 range키워드 다음의 채널로부터 계속 수신하다가 채널이 닫힌 것을 감지하면 for 루프를 종료한다.
Go의 select문은 복수 채널들을 기다리면서 준비된 채널을 실행하는 기능을 제공한다. 즉, select문은 여러개의 case문에서 각각 다른 채널을 기다리다가 준비가 된 채널 case를 실행하는 것이다.
select문은 case채널들이 준비되지 않으면 계속 대기하게 되고, 가장 먼저 도착한 채널의 case를 실행한다. 만약 복수 채널에 신호가 오면, Go 런타임이 랜덤하게 그 중 한 개를 선택한다. 하지만 select문에 default 문이 있으면, case문 채널이 준비되지 않더라도 계속 대기하지 않고 바로 default문을 실행한다.
package channeltest
import (
"fmt"
"time"
)
func Channeltest6() {
done1 := make(chan bool)
done2 := make(chan bool)
go run1(done1)
go run2(done2)
EXIT:
for {
select {
case <- done1:
fmt.Println("run1 완료")
case <- done2:
fmt.Println("run2 완료")
break EXIT
}
}
}
func run1(done chan bool) {
time.Sleep(1 * time.Second)
done <- true
}
func run2(done chan bool) {
time.Sleep(2 * time.Second)
done <- true
}