go에서 channel은 채널 연산자인 <-를 통해서 데이터를 주고 받는 통로로 사용됩니다.
주로 goroutines 사이에서 데이터를 주고 받는데 상대편이 준비될 때까지 채널에서 대기함으로써 Lock을 사용하지
않고 데이터를 동기화 할 수 있습니다.
func main() {
// string형 채널을 생성합니다.
channel := make(chan string)
// goroutines을 실행
go func(str string) {
// 전달 받은 변수를 수정해서 channel에 전달
channel <- "My name is " + str
}("Ahn")
// channel을 통해서 데이터를 전달 받습니다.
result := <-channel
println(result)
}
위의 예제를 보면 string channel타입의 channel 변수를 선언한 후 goroutine과 main함수 사이에서 데이터를
주고 받는 모습을 볼수 있습니다.
채널 연산자 사용법 | 설 명 |
[채널] <- [값] |
채널에 값을 Send 합니다. 채널 Send연산자 호출 대상은 channel에 종류에 따라서 다르게 동작합니다. 만약 unbuffered 채널이라면 수신자가 받을 때까지 대기합니다. buffered 채널이고 공간이 남아 있다면 값 복사 비용만큼만 대기합니다. |
[값] = <-[채널] |
채널에서 값을 Recv 합니다. 채널 Recv연산자 호출 대상은 언제나 데이터를 들어 올 때까지 대기하게 됩니다. |
Channel의 버퍼링 기능
channel을 생성할 때 buffered와 unbuffered channel로 생성할 수 있습니다. 위의 예제의 channel은 unbuffered로
생성되었습니다. 이름과 같이 저장공간이 없기 때문에 송신자(Sender)는 수신자(Reciever)가 데이터를 받을 때까지
대기합니다. buffered channel은 선언된 저장 공간만큼 송신자는 데이터를 보내고 다른 작업(비동기)을
계속 할 수 있습니다.
// unbuffered 생성
channel := make(chan string)
// 10개의 buffer를 가진 channel생성
channel := make(chan string, 10)
buffered channel을 생성하는 방법은 make 함수에 두번째 인자에 버퍼의 갯수을 전달 하는 것입니다.
Channel의 파라미터
channel또한 함수의 파라미터로 전달 할 수 있는데 default로 송수신이 가능한 채널로 전달하지만
송신또는 수신만 가능한 channel로 전달 할 수 있습니다.
//OnlySendChannel 함수는 인자로 송신만 가능한 채널을 전달합니다.
func OnlySendChannel(ch chan<- string){
// 채널에 string을 송신함
ch <- "Only Send ch"
// error 발생 ch에서 수신 못함!!
<- ch
}
// OnlyRecvChannel 함수는 인자로 수신만 가능한 채널을 전달합니다.
func OnlyRecvChannel(ch <-chan string){
// 채널에 string을 수신함
value := <- ch
println(value)
// error 채널에 string을 송신 못함
ch <- "receive complete"
}
위의 예제를 실행 시켜보면 error를 발생시키는 것을 볼수 있습니다. 이렇게 파라미터를 지원함으로써
조금 더 안전한 channel의 사용이 가능합니다.
Channel Range와 Close
송신자(sender)는 더 이상 데이터를 보낼 것이 없으면 channel을 close 할 수 있습니다.
수신자(receiver)는 receive 표현식의 두번째 파라미터를 통해서 channel이 닫힌 상태인지 확인 할 수 있습니다.
(주의 수신자는 채널을 닫으면 안됩니다)
(채널은 파일과 달리 굳이 닫을 필요는 없지만 수신자에 닫힘을 알려야 할 때 close를 호출합니다.)
// ok 변수는 채널이 상태를 표현합니다.
v, ok := <-ch
예제에서 ok 변수를 통해서 채널의 닫힌 여부를 확인 할 수 있습니다. 더이상 수신할 값이 없으며 channel이
닫혀있다면 ok가 false입니다.
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
"for i := range <채널변수>" 를 사용하면 채널이 닫힐 때까지 채널에서 반복적으로 값을 받을 수 있습니다.
Channel Select
go에서 select문은 복수의 채널들을 기다리면서 준비상태가 된 채널을 실행하는 기능을 가지고 있습니다.
select 구문은 case중에 하나가 실행 될 수 있을 때까지 block한 다음에 해당 case를 실행합니다.
만약에 여러개가 준비되었다면 무작위로 하나를 선택합니다.
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c, " i = ", i)
if i == 8 {
quit <- 1
}
}
quit <- 0
}()
fibonacci(c, quit)
}
만약에서 select 문에 default 문이 존재하면 case문이 준비되지 않더라도 계속 대기하지 않고 바로 default문을
실행합니다.
func main() {
// 200밀리초 이후에 켜집니다.
tick := time.Tick(200 * time.Millisecond)
// 500밀리초 이후에 켜집니다.
boom := time.After(500 * time.Millisecond)
StartTime := time.Now()
for {
select {
case <-tick:
fmt.Println("on tick time")
case <-boom:
fmt.Println("on BOOM!")
return
default:
// case가 준비 되지 않았다면 시간을 찍고 100 밀리초 대기합니다.
t := time.Now()
fmt.Println(t.Sub(StartTime).Milliseconds(), "over millisec.")
time.Sleep(100 * time.Millisecond)
}
}
}