개요
고루틴을 사용하면서 데드락을 경험하거나, 어떻게 다른 채널을 종료시킬지 고민할 때가 있다. 지금 소개하는 패턴을 사용하게 되면, 부모 고루틴과 자식 고루틴 사이에 어떻게 close 신호를 전달할지 이해할 수 있다.
문제 발생
- 고루틴들은 런타임에 의해 가비지 컬렉션되지 않으므로 Memory Leak이 발생할 가능성이 있다.
- 메모리상에서 고루틴들이 종료되게 제어할 수 있는 방법이 필요하다.
- 그렇다면, 부모 고루틴들이 자식 고루틴들을 종료할 수 있게 만들어 주면 된다.
읽기 채널의 고루틴 제어
변경 전
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main(){
doWork := func(strings <-chan string) <-chan struct{} {
completed := make(chan struct{})
go func(){
defer close(completed)
for s := range strings {
fmt.Println(s)
}
}()
return completed
}
terminated := doWork(nil)
<-terminated
//DeadLock 발생
fmt.Println("Done")
}
- 위와 같은 경우 nil 채널에 string이 아무것도 들어오지 않기 때문에 doWork 고루틴은 프로세스가 종료될 때 까지 아무것도 수행하지 않는다.
- 심지어 terminated 채널에 아무것도 들어오지 않기 떄문에 데드락 상태가 발생한다.
- 먄약 위 main 프로세스가 계속 돌아가는 상태라면 doWork 고루틴은 메모리를 계속 잡고 있게 된다. -> Memory Leak
변경 후
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main(){
doWork := func(done <-chan struct{}, strings <-chan string) <-chan struct{} {
completed := make(chan struct{})
go func(){
defer close(completed)
for s := range strings {
fmt.Println(s)
}
}()
return completed
}
done := make(chan struct{})
terminated := doWork(done,nil)
go func(){
time.Sleep(time.Second)
close(done)
}
<-terminated
fmt.Println("Done")
}
- 부모 고루틴이 자식 고루틴에게 cancel 신호를 보낼 수 있게 신호를 설정하였다.
- 이 상황에서는 위와 똑같이 strings 채널에 아무것도 들어가지 않더라도, 부모 고루틴에서 자식 고루틴을 제어할 수 있는 done 채널이 존재하기 떄문에 고루틴 누수를 제거할 수 있다.
쓰기 채널의 고루틴 제어
변경 전
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main(){
newRandStream := func() <-chan int{
randStream := make(chan int)
go func(){
defer fmt.Println("newRandStream closure exited")
defer close(randStream)
for {
randStream <- rand.Intn(100)
}
}()
return randStream
}
randStream := newRandStream()
for i:=0; i<3; i++{
fmt.Println(<-randStream)
}
}
/*
OUTPUT
81
87
47
*/
- 위와 같은 상황에서는 쓰기 채널을 제어할 수 없기 때문에 3개의 Random int를 받고 나서도 계속 randStream 채널에는 데이터가 들어오게 된다.
- 채널은 종료되지 않아서 defer로 호출한 Message가 출력되지 않았다.
변경 후
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func main(){
newRandStream := func(done <-chan struct{}) <-chan int{
randStream := make(chan int)
go func(){
defer fmt.Println("newRandStream closure exited")
defer close(randStream)
for {
select{
case <-done:
return
case randStream <- rand.Intn(100):
}
}
}()
return randStream
}
done := make(chan struct{})
randStream := newRandStream(done)
for i:=0; i<3; i++{
fmt.Println(<-randStream)
}
<-randStream
time.Sleep(time.Second)
}
/*
OUTPUT
81
87
47
newRandStream closure exited
*/
- 여기서도 done 채널을 주어서 부모 고루틴에서 쓰기 채널을 종료할 수 있게 만들어 주었다.
for-select 루프
- 채널에서 반복된 변수를 보내고자 할 때
1
2
3
4
5
6
7
for _, s := range []string{"a","b","c","d"}{
select {
case <-done:
return
case stringStream <-s:
}
}
- 멈추기를 기다리면서 무한 대기
1
2
3
4
5
6
7
8
for {
select {
case <-done:
return
default:
//Wait Infinite
}
}