값이 들어오는 channel이 있을 때 단순하게 for range 문을 사용해서 그 채널에서 값을 받아오게 되면, done채널을 사용해서 취소하기가 쉽지 않다. 그렇기 때문에 아래와 같이 for 와 select 문을 함께 사용한다. 기존에 4번 글(https://sea-green.tistory.com/25 )에서 다뤘던 부분 중 Done channel이 닫히기 전까지 무한루프를 돌며 작업을 수행하는 코드이다. done 채널을 포함한 select 문을 사용하는 것이다.
//1.
for {
select {
case <-done:
return
default:
}
// Do non-preemptable work
}
//2.
for {
select {
case <-done:
return
default:
// Do non-preemptable work
}
}
이런 코드 형태를 따라 이런 식으로 할 수 있다.
for {
select{
case v := <-valStream:
//do something with v
case <-done:
return
}
}
이런 것이 반복되게 되면, verbosity가 커지기 때문에 이를 encapsulate 할 수 있는 방법을 소개한다.
요구사항이 아래와 같을 때
- myChan에서 들어오는 값들을 처리해야하고,
- 처리를 하다가 done channel을 통해서 취소할 수 있어야 하며,
- myChan이 close되었을 때 작업이 끝나야한다.
orDone 함수를 사용해서 encapsulate할 수 있다.
orDone := func(done, c <-chan interface{}) <-chan interface{} {
valStream:= make(chan interface{})
go func() {
defer close(valStream)
for {
select {
case <- done:
return
case v, ok := <-c:
if ok == false {
return
}
select {
case valStream <- v:
case <-done:
}
}
}
}()
return valStream
}
for val := range orDone(done, myChan) {
// Do something with val
}
아래 for loop과 같이 간단하게 쓸 수 있다.
두번째 select 문이 있는 이유는 무엇일까? valStream <- v만 하지 않고, select문을 추가한 이유는?
만약에 done 채널로 인해서 canceled되고 싶은데, 만약에 valStream이 읽히지 않아서 block 된다면, cancel되지 않을 수 있다. block 되지 않고 취소가 되도록하기 위해 안에 select 문이 추가가 되는 것이다. 만약 없이 valStream <- v 만 있었다면, block되어 done이 close되었어도 취소되지 않았을 것이기 때문에 무조건 이 부분이 있어야 한다.
'Backend with Golang' 카테고리의 다른 글
[Concurrency in Go] 비정상 고루틴의 치료 (steward & ward) (0) | 2023.11.10 |
---|---|
[MySQL] Update Data in Batches (0) | 2023.09.19 |
[Concurrency-5] Fan-Out, Fan-In (0) | 2023.09.12 |
Rate Limiting (1) | 2023.09.06 |
Effective Go - Part 4: Embedding & Concurrency (0) | 2023.08.16 |