개요
- 버퍼 없는 채널 사용시 쓰기, 읽기가 동시에 일어나지 않으면 블락이 발생할 수 있음
- select를 통해 비동기로 구현
- 아래 예시는 이번 주제를 설명하기에 적절하지 않을 수 있음 (채널과 고루틴 없이 충분히 구현 가능)
- 하지만 핵심 부분만 간단하기 설명하기 위함
- 실제 참고한 코드를 보는 걸 추천 (참고문헌의 loop 함수)
코드 및 출력
- 예시 프로그램
- 1초 주기로, 무작위 수를 발생시켜, 짝수일 경우 출력
- 10초 이후부터 출력
동기 코드
package study
import (
"fmt"
"math/rand"
"time"
)
type Item struct {
num int
}
func ChanWithSync() {
externalItemChan := make(chan Item)
go func() {
for {
<-time.After(time.Second)
result := rand.Intn(10)
if result%2 == 0 {
fmt.Println("Add start")
externalItemChan <- Item{num: result}
fmt.Println("Add end")
}
}
}()
time.Sleep(10 * time.Second)
for {
item := <-externalItemChan
fmt.Println(item.num)
}
}
Add start
# After 10 seconds
Add end
8
Add start
Add end
0
Add start
Add end
6
Add start
Add end
0
- 분석
- 외부 채널(externalItemChan)에 Item을 넘겨주는 순간부터, 고루틴이 블락
- 10초간 모든 고루틴이 정지됨
비동기 코드
package study
import (
"fmt"
"math/rand"
"time"
)
type Item struct {
num int
}
func ChanWithAsync() {
externalItemChan := make(chan Item)
go func() {
randNumChan := make(chan int)
evenNumSlice := []int{}
for {
var item Item
var itemChan chan Item
if len(evenNumSlice) > 0 {
item = Item{num: evenNumSlice[0]}
itemChan = externalItemChan
}
select {
case <-time.After(time.Second):
go func() {
randNumChan <- rand.Intn(10)
}()
case result := <-randNumChan:
if result%2 == 0 {
fmt.Println("Add start")
evenNumSlice = append(evenNumSlice, result)
fmt.Println("Add end")
}
case itemChan <- item:
evenNumSlice = evenNumSlice[1:]
}
}
}()
time.Sleep(10 * time.Second)
for {
item := <-externalItemChan
fmt.Println(item.num)
}
}
Add start
Add end
Add start
Add end
Add start
Add end
# After 10 seconds
8
0
6
Add start
Add end
0
Add start
Add end
4
- 분석
- 무작위 수 생성 시 채널(randomNumChan)을 사용하고, 별도의 고루틴으로 데이터를 넣기 때문에 1초당 무작위 수 생성 보장하며, 블락되지 않음
- 출력할 값을 슬라이스에 저장하여, 나중에 한번에 데이터를 넘겨줄 수 있음
- 외부 채널(externalItemChan)에 Item을 넘겨주는 과정에서 블락되더라도, 다른 작업에 영향을 끼지치 않음
결론
- select로 채널의 값을 읽기뿐만아니라, 쓰기도 가능
- 채널이 nil일 경우 무시(읽기, 쓰기)
- 채널이 블락된 경우 무시(읽기, 쓰기)
참고문헌