티스토리 뷰

golang

go 동시성 패턴: or-done-channel 정리

주먹불끈 2020. 7. 30. 15:51


Photo by Emile Perron on Unsplash



개요

 

Concurrency in Go 책의 http://aladin.kr/p/YLCKv

4 패턴중에서 or-done-channel 정리해본다.

 

참고링크: https://jacking75.github.io/go_or-done-channel/ 

참고링크: https://stackoverflow.com/questions/60491622/why-does-this-ordone-channel-implementation-receive-twice-from-done-channel

 

상황

 

값을 읽어서 처리할 채널 (myChan 이라고 하자) 있고,

언제든 작업을 중단하도록 요청이 들어올 있는 채널 (done 이라고 하자) 있다고 하자.

 

보통의 경우는 우리는 myChan 통해 어떻게 데이터가 들어오거나 중단될지, done 어떤 타이밍에 들어올 알고 있지만

막연하게 myChan done 채널 만을 받아서 그에 맞게 동작하도록 구현해야할 경우가 생긴다.

 

예를 들면, 이런 요구사항인 것이다.

1) myChan 채널을 줄께. 그러면 채널로 값이 들어오면 특정 작업을 해줘

2) 그런데 myChan 언젠가 close 있어. 그러면 당연히 작업은 끝나야 하지

3) 마지막으로 done 채널도 별도로 줄게. 언제든 이녀석이 close 되거나 값을 읽게 되면 작업을 중단해줘

 

씸플하게 for range, 하지만...

 

채널 (여기서는 myChan) 통해서 들어오는 값들을 처리하는건 for range 무척 편리하다.

 

1) myChan 통해서 값이 들어올때마다 알아서 작업을 해준다.

2) 심지어는 myChan close 되는지도 자동으로 알아서 loop 종료해준다

 

하지만 고루틴의 메모리 누수 가능성을 고려하면 별도의 done 채널을 포함한 select 문을 사용하는 것이 좋다.

- 고루틴을 생성한 놈이 고루틴의 종료까지도 챙겨야 한다는 암묵적인 원칙이 있다.

고루틴의 메모리 누수를 방지하기 위한 원칙이라고 보면 된다.

- 이를 위해 필요한 것이 아래의 done 채널이다.

 

1) done 채널이 close 되거나, 값이 들어오게  되면 즉각 루프를 빠져나오게 된다.

 

2) myChan 채널을 읽는데

- 우선 close 여부를 체크하여 close 되었다면 루프를 빠져나온다

- 그렇지 않다면 읽은 값으로 작업을 한다.

 

고루틴을 사용하는데 있어서의 원칙

 

코드는 동작하지만 뭔가 찝찝하고 지저분하다.

이런 코드가 소스 여기저기에 널려있다면 보기 좋지 않다.

이러한 패턴은 자주 쓰일 있으니 orDone() 이라는 함수안에 밀어넣어 버리자.

 

원칙을 짚고 넘어가자

 

1) Golang 에서의 동시성 코드는 가독성이 핵심이다

2) 고루틴은 마구 퍼날라 있는 자원으로 생각하자

다시말해 최적화에 대한 고민은 나중의 나중으로 미루자.

너무 이른 최적화 (premature optimization) 하지 말라는 것이다.

 

 https://gist.github.com/nicewook/46dbb771a2755044f4d6daa45fa8abba

 

리턴값부터 보자.

 

1) chan interface{} 이다.

2) 값을 읽을 있거나, close 있는 채널 하나 것이다.

 

orDone() 함수는 결국

 

1) myChan done 채널을 받아서

2) 읽을 있는 채널 하나 만들어 리턴해주는데

 

3) myChan 통해서 값을 읽으면 값을 리턴된 채널 써서, 채널로 읽을 있게 해주고

4) myChan close 되거나 done 받게 되면 리턴된 채널 역시 close 되게 하는 것이다.

 

씸플한 for range 귀환

 

드디어 우리는 orDone() 이용하여 씸플한 코드 + done 이용한 고루틴 누수방지에  성공한 것이다.

 


 

마지막 의문. 번째 select

 

정답 링크: https://stackoverflow.com/questions/60491622/why-does-this-ordone-channel-implementation-receive-twice-from-done-channel

 

번째 select 문이 필요한걸까? 그냥 valStream v 해줘도 되지 않나?

 

1) valStream return 되어 외부에서 읽혀질 채널이다.

2) 그런데  valStream v 값을 넣어주려는데 외부에서 읽지 않고 있다면? 블락이다!

- 여기서 블락된 상태라면 두번째 <-done 없다면 select 문에 아무 선택지가 없는 상황이 되는 것이다. (매우 중요한 포인트)

 

Block 테스트 코드

 

- Go Playground: https://play.golang.org/p/rlqS9fSScO0

- 아래에 코드를 설명해본다.

 

repeat 함수 설명

 

파라미터로 done 채널과 values 슬라이스를 받는다

그리고 채널을 리턴하는데 채널은

 

1) values 슬라이스의 값들을 반복해서 리턴된 채널에 써준다

- 예를 들어 []int{1,2,3,4,5} 받으면

- 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, … 무한정 쓴다

2) 혹은 done 채널이 close 되거나 값이 들어오면 리턴된 채널을 close 해준다.

 

테스트에서의 역할

 

테스트에서는 done 채널을 close 하지 않는다.

, 리턴하는 채널에다 무한히 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, … 쓴다.

orDone 함수 설명

 

함수는 이미 위에서 설명을 해두어서 생략한다.

내용들은 repeat, orDone 함수에 대한 정의였고

이제부터 실제 테스트를 보겠다.

 

1) doneOrDone 채널은 orDone 함수로 넘겨진다.

- 그리고 go func() 통해 대략 2 뒤에 close 된다.

- 다시말해 대략 2초뒤에 orDone 함수는 종료된다.

 

2) repeat 함수를 통해 generator 채널을 생성한다.

- 채널은 무한정 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, …를 읽을 있게 된다.

 

부분이 가장 중요한 포인트이다

3) 하지만 for range 루프는 오직 번만 돌고 break 통해 튕겨나온다.

- , orDone 함수로 리턴되는 채널을 번만 읽는 다는 것이다.

- 이렇게 되면 orDone 함수로 리턴되는 채널에는 세번째 까지는 있지만

네번째 쓰기를 하려면 블락된 채널이어서 select 문에서 없는 case 마찬가지가 된다.

 

코드를 돌리면 아래 오른쪽과 같은 가지 출력을 얻게 된다.

왼쪽 코드, orDone 함수의 핵심부를 보면서 차근히 따라가보자

 

1) 처음 번은 orDone 으로  return 되는 채널인 valStream 으로 쓰여진다.

- get data from stream and write to output stream 부분이다.

- 하지만 세번째 쓰여진 값은 읽어가지 않기에 이때부터 case valStream <- v:  블락이 되어 select 문에서 아예 없는 case 보면 된다.

2) 이 상황에서 c 에서 값을 읽게 되면 select 문에서는 오직 case <-done: 만을 기다리게 된다.

- 위에서 언급한대로 대략 2초뒤에 close(doneOrDone) 실행되면 select 문에서 통과가 된다.

done. possibly unleash blocked code 부분이다.

- 이 다음에 done 먼저 읽는가 아니면 <-c 먼저 읽는 가에 따라

done. possibly unleash blocked code 쓰여질 있다.

* 써놓고 나니 두번째 <-done 에서도 바로 return 해줘도 하다.

 


 


반응형
반응형
잡학툰 뱃지
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
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
글 보관함